WcfService:单服务多契约接口以及用户名密码认证

2024-03-16 12:10

本文主要是介绍WcfService:单服务多契约接口以及用户名密码认证,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

      很久没写过博客了,以往写博客都是直接在网页上写的,效率比较低。今天刚好研究了下Windows Live Writer,写博客要方便了很多。所以打算将这一年来研究的部分成果发一发。

      这一段时间主要是研究了服务端开发的框架,包括Web Service、Web API以及WCF,通过VS实现Web Service是最容易的,适合轻量级的Web服务,Web API因为之前用了很久的Asp.net MVC,所以学起来很快,WCF难度最大,框架也较为复杂。在学习使用WCF的过程中趟过了很多坑,通过今天这个单服务多契约接口及用户名密码认证的实例来给自己做个背书吧。

项目结构

      如下图所示,我构建了一个名为“ywt.WcfService”的解决方案,并在其下建立了四个项目:

1

      各项目的名称及功能描述如下表:

名称类型功能描述
ywt.WcfService.Interfaces类库项目包含所有的WcfService契约接,为简单期间,我仅做了2个契约接口
ywt.WcfService.SelfHost控制台应用程序项目对WcfService服务进行自寄宿,相关的服务配置都在该项目的App.Config文件中
ywt.WcfService.Service类库项目服务实现代码
ywt.WcfService.WinFormClientWinForm应用程序项目Winform客户端程序

ywt.WcfService.Interfaces接口项目

      该项需要引用System.ServiceModel和System.Runtime.Serialization。包含2个接口的定义:ICalculator.cs、ILog.cs,做为演示,代码非常简单。代码分别如下所示:

ICalculator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;namespace ywt.WcfService.Interfaces
{[ServiceContract]public interface ICalculator{[OperationContract]double Add(double param1, double param2);}
}

ILog.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;namespace ywt.WcfService.Interfaces
{[ServiceContract]public interface ILog{[OperationContract]string Log(string text);}
}

ywt.WcfService.Service服务实现项目

      该项需要引用System.ServiceModel和System.Runtime.Serialization,另外还需要引用ywt.WcfService.Interfaces接口项目。在该项目中需要实现接口项目中定义的所有接口,我们可以通过一个服务来实现所有的接口。一个服务实现所有的接口时,可能会出现服务代码过于臃肿,不便于查看维护,我们可以将我们的服务实现类拆分成多个部分类,并为各个部分类的文件名(注意是文件名而不是类名)取一个对应的名称。

      我做了2个类,文件名分别为CalculatorService.cs和LogService.cs,2个文件中分别用于实现不同的接口。2个类文件的代码如下:

CalculatorService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;
using System.ServiceModel;
using ywt.WcfService.Interfaces;namespace ywt.WcfService.Services
{public partial class MyService : ICalculator{public double Add(double param1, double param2){return param1+param2;}}
}

LogService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ywt.WcfService.Interfaces;namespace ywt.WcfService.Services
{public partial class MyService : ILog{public string Log(string text){return $"ServiceLog: {text}";}}
}

ywt.WcfService.SelfHost服务寄宿控制台项目

      该项需要引用ywt.WcfService.Interfaces、ywt.WcfService.Service两个项目,以及System.ServiceModel、System.Runtime.Serialization、System.IdentityModel和System.IdentityModel.Selectors。我采用是消息安全模式,客户端认证方式是用户名密码,此时服务端必须设置证书,为了省去创建证书这一步,我直接使用服务端的本机localhost证书,该证书在服务端安装操作系统时自动创建。

      根据以上情况,在本项目中需要做三件事情:

  1. 在Program.cs中实现服务寄宿的代码(另外我在这个环节中通过代码设置了证书,其实也可以通过配置来实现)。
  2. 创建自定义的UserNamePasswordValidator,用于验证用户名和密码,实际应用中需要读取数据库,我这里直接进行的静态比较。
  3. 设置配置文件

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Security.Cryptography.X509Certificates;
using ywt.WcfService.Services;namespace ywt.WcfService.SeftHost
{class Program{private static ServiceHost host;static void Main(string[] args){Console.WriteLine("Wcf服务开始启动");try{host = new ServiceHost(typeof(MyService));ServiceCredentials scs = host.Description.Behaviors.Find<ServiceCredentials>();if (scs == null){scs = new ServiceCredentials();host.Description.Behaviors.Add(scs);}scs.ServiceCertificate.SetCertificate("CN=localhost",StoreLocation.LocalMachine,StoreName.My);host.Open();Console.WriteLine("Wcf服务启动成功");Console.ReadKey();}catch (Exception ex){Console.WriteLine($"Wcf服务启动失败: {ex.Message}");Console.ReadKey();}finally{host.Close();}            }}
}

MyUserNamePasswordValidator.cs

using System;
using System.Collections.Generic;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ywt.WcfService.SeftHost
{public class MyUserNamePasswordValidator : UserNamePasswordValidator{public override void Validate(string userName, string password){if (userName != "admin" || password != "admin"){throw new SecurityTokenValidationException("用户未获得授权");}}}
}

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration><startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /></startup><system.serviceModel><bindings><wsHttpBinding><binding name="msgUserNameHttp"><security><message clientCredentialType="UserName" negotiateServiceCredential="true"/></security></binding></wsHttpBinding></bindings><services><service behaviorConfiguration="behavior1" name="ywt.WcfService.Services.MyService"><endpoint name="Calculator" address="" binding="wsHttpBinding" bindingConfiguration="msgUserNameHttp"contract="ywt.WcfService.Interfaces.ICalculator" /><endpoint name="Log" address="" binding="wsHttpBinding" bindingConfiguration="msgUserNameHttp" contract="ywt.WcfService.Interfaces.ILog" /><host><baseAddresses><add baseAddress="http://127.0.0.1:9876/MyService" /></baseAddresses></host></service></services><behaviors><serviceBehaviors><behavior name="behavior1"><serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:9876/MyService/MEX" /><serviceCredentials><userNameAuthentication userNamePasswordValidationMode="Custom"customUserNamePasswordValidatorType="ywt.WcfService.SeftHost.MyUserNamePasswordValidator,ywt.WcfService.SeftHost"/></serviceCredentials></behavior></serviceBehaviors></behaviors></system.serviceModel>
</configuration>

ywt.WcfService.WinFormClient客户端项目

      该项需要引用System.ServiceModel、System.Runtime.Serialization,调用Wcf服务我们采用引用代理。首先得将ywt.WcfService.SelfHost运行起来,注意不能直接在VS中直接调试运行,需要编译该项目后,找到生成的exe程序启动。随后我们可以为当前的客户端添加服务引用:

2

      在地址中录入正确的服务地址,然后点击转到,在服务列表框中我们可以看到我们的MyService,其下包含了2个我们定义的接口。在命名空间中录入自定义的命名空间文本,假如在此处录入了WcfServices,那么实际最后完整的命名空间完整路径是:ywt.WcfService.WinFormClient.WcfServices。也就是说此处的命名空间不需要写成完全的,VS会自动补全,将当前客户端项目的命名空间加在前面。

      客户端仅添加了一个窗体,窗体上放置了2个文本框、2个Lable以及一个按钮控件。界面如下所示:

3

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.ServiceModel.Security;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ywt.WcfService.WinFormClient.WcfServices;namespace ywt.WcfService.WinFormClient
{public partial class Form1 : Form{public Form1(){InitializeComponent();}private async void button1_Click(object sender, EventArgs e){            CalculatorClient calculator = new CalculatorClient();UserNamePasswordClientCredential credential = calculator.ClientCredentials.UserName;credential.UserName = "admin";credential.Password = "admin";LogClient log = new LogClient();double p1, p2;double.TryParse(textBox1.Text, out p1);double.TryParse(textBox2.Text, out p2);p1=await calculator.AddAsync(p1, p2);label1.Text= p1.ToString();credential = log.ClientCredentials.UserName;credential.UserName = "admin";credential.Password = "admin";label2.Text= await log.LogAsync(label1.Text);}}
}

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration><startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /></startup><system.serviceModel><behaviors><endpointBehaviors><behavior name="NewBehavior0"><clientCredentials><serviceCertificate><authentication certificateValidationMode="None" revocationMode="NoCheck" /></serviceCertificate></clientCredentials></behavior></endpointBehaviors></behaviors><bindings><wsHttpBinding><binding name="Calculator"><security><message clientCredentialType="UserName" /></security></binding><binding name="Log"><security><message clientCredentialType="UserName" /></security></binding></wsHttpBinding></bindings><client><endpoint address="http://127.0.0.1:9876/MyService" binding="wsHttpBinding"bindingConfiguration="Calculator" contract="WcfServices.ICalculator" behaviorConfiguration="NewBehavior0"name="Calculator"><identity><certificate encodedValue="AwAAAAEAAAAUAAAAemLPWHcq5CeL/jln/1OjQSeKL/QgAAAAAQAAAPACAAAwggLsMIIB1KADAgECAhAdf5gB+4wxqEgNTFZJZ+SUMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xODA0MTkxMzIzMTZaFw0yMzA0MTkwMDAwMDBaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1T6iuo0M8fsCuazkDkD/sPPICeJrabdoq1B/0/xIKSNV+IFPnncPdefefh6ZOdyaPXN9yF+/v6GIveLEseQ8w0oXC5l+Eyl7kXTc3xzysLaKL/rFtjUH91+6qKjE9un5C+bVp884zQnOKhqDXxiqn6Aoem2kjAWbo0244weA2VE5kQZHAEsd2PrZpcy8gLptmtPc5Kqp1UuyVRmdTkmm2HZD3GQmgmASf5LUtgTAtcxLEjAQ4dtzyoBPnAL8meR6mgbj/JKOXutyY/QRxxfYun+sBDIJArL3tBnKQTBHJxCLuU8j0dSGYCfCyvaMNgXQWL1G4SjG9LAKQkj3c+LkcCAwEAAaM6MDgwCwYDVR0PBAQDAgSwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAVPCHXEqNMnsZ5uCQ0y7iNvR9aQgZTGmWdn/c2GH39VqJ1bJpToTZm5SQeJYCLUW2f0bDq1JbLWSaRG8c9PREvYsIlOtrJdyhxplBOkdcs+zyx2BQC7tlCWaoDjGS1SEVAu48NrspktB6rh3KOjuoxcr5vWO1G76zYaSAQ2At/5+VIINxkg8/tk6JF3wEq63qdrRgVUCru0Yi0cVU0UViVPVWl61LrrERenRHT1YhldwwpPDQC38qLnE6YREQzEzEHEzoeBWU1dj65/X5b53v6B7jqm5cXhuAvZZMt8Kvo1HzWVwHDmOD3VMoEPR3aXCjXZ5WK9AHXsOrH3SKjPsXIQ==" /></identity></endpoint><endpoint address="http://127.0.0.1:9876/MyService" binding="wsHttpBinding"bindingConfiguration="Log" contract="WcfServices.ILog" behaviorConfiguration="NewBehavior0" name="Log"><identity><certificate encodedValue="AwAAAAEAAAAUAAAAemLPWHcq5CeL/jln/1OjQSeKL/QgAAAAAQAAAPACAAAwggLsMIIB1KADAgECAhAdf5gB+4wxqEgNTFZJZ+SUMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xODA0MTkxMzIzMTZaFw0yMzA0MTkwMDAwMDBaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1T6iuo0M8fsCuazkDkD/sPPICeJrabdoq1B/0/xIKSNV+IFPnncPdefefh6ZOdyaPXN9yF+/v6GIveLEseQ8w0oXC5l+Eyl7kXTc3xzysLaKL/rFtjUH91+6qKjE9un5C+bVp884zQnOKhqDXxiqn6Aoem2kjAWbo0244weA2VE5kQZHAEsd2PrZpcy8gLptmtPc5Kqp1UuyVRmdTkmm2HZD3GQmgmASf5LUtgTAtcxLEjAQ4dtzyoBPnAL8meR6mgbj/JKOXutyY/QRxxfYun+sBDIJArL3tBnKQTBHJxCLuU8j0dSGYCfCyvaMNgXQWL1G4SjG9LAKQkj3c+LkcCAwEAAaM6MDgwCwYDVR0PBAQDAgSwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAVPCHXEqNMnsZ5uCQ0y7iNvR9aQgZTGmWdn/c2GH39VqJ1bJpToTZm5SQeJYCLUW2f0bDq1JbLWSaRG8c9PREvYsIlOtrJdyhxplBOkdcs+zyx2BQC7tlCWaoDjGS1SEVAu48NrspktB6rh3KOjuoxcr5vWO1G76zYaSAQ2At/5+VIINxkg8/tk6JF3wEq63qdrRgVUCru0Yi0cVU0UViVPVWl61LrrERenRHT1YhldwwpPDQC38qLnE6YREQzEzEHEzoeBWU1dj65/X5b53v6B7jqm5cXhuAvZZMt8Kvo1HzWVwHDmOD3VMoEPR3aXCjXZ5WK9AHXsOrH3SKjPsXIQ==" /></identity></endpoint></client></system.serviceModel>
</configuration>

运行效果

4     5

提出问题

      通过以上客户端调用Wcf服务时可以看到,每当调用一个契约的本地代理时,都得传递用户名和密码,这实在是坑爹的办法,因为实际的项目中,一个服务中可能会需要实现无数个契约接口,如果每用一次契约就得传一次用户名密码,真是让人无法忍受。所以现在就有了一个新的问题,如何只需传递一次用户名和密码?网上有将用户名和密码写入SOAP消息头的做法,但是这种方法并不推荐。推荐的方式是做一个契约专门完成登录退出,在该契约的Login方法中我们为完成正确登录的用户分发一个令牌(由服务端生成的具有一定时效的字符串),然后将该令牌写入SOAP消息头,随后客户端和服务端的通信认证都由这个令牌来识别。这种模式我在Web Service、Web API里都搞过,但是WCF还没试过实现。所以也不提供代码了。

转载于:https://www.cnblogs.com/alexywt/p/9863117.html

这篇关于WcfService:单服务多契约接口以及用户名密码认证的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/815451

相关文章

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

Spring Boot 与微服务入门实战详细总结

《SpringBoot与微服务入门实战详细总结》本文讲解SpringBoot框架的核心特性如快速构建、自动配置、零XML与微服务架构的定义、演进及优缺点,涵盖开发环境准备和HelloWorld实战... 目录一、Spring Boot 核心概述二、微服务架构详解1. 微服务的定义与演进2. 微服务的优缺点三

RabbitMQ消息总线方式刷新配置服务全过程

《RabbitMQ消息总线方式刷新配置服务全过程》SpringCloudBus通过消息总线与MQ实现微服务配置统一刷新,结合GitWebhooks自动触发更新,避免手动重启,提升效率与可靠性,适用于配... 目录前言介绍环境准备代码示例测试验证总结前言介绍在微服务架构中,为了更方便的向微服务实例广播消息,

关于DNS域名解析服务

《关于DNS域名解析服务》:本文主要介绍关于DNS域名解析服务,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录DNS系统的作用及类型DNS使用的协议及端口号DNS系统的分布式数据结构DNS的分布式互联网解析库域名体系结构两种查询方式DNS服务器类型统计构建DNS域

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

java向微信服务号发送消息的完整步骤实例

《java向微信服务号发送消息的完整步骤实例》:本文主要介绍java向微信服务号发送消息的相关资料,包括申请测试号获取appID/appsecret、关注公众号获取openID、配置消息模板及代码... 目录步骤1. 申请测试系统2. 公众号账号信息3. 关注测试号二维码4. 消息模板接口5. Java测试

SpringBoot+Redis防止接口重复提交问题

《SpringBoot+Redis防止接口重复提交问题》:本文主要介绍SpringBoot+Redis防止接口重复提交问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录前言实现思路代码示例测试总结前言在项目的使用使用过程中,经常会出现某些操作在短时间内频繁提交。例

PostgreSQL数据库密码被遗忘时的操作步骤

《PostgreSQL数据库密码被遗忘时的操作步骤》密码遗忘是常见的用户问题,因此提供一种安全的遗忘密码找回机制是十分必要的,:本文主要介绍PostgreSQL数据库密码被遗忘时的操作步骤的相关资... 目录前言一、背景知识二、Windows环境下的解决步骤1. 找到PostgreSQL安装目录2. 修改p

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置