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

相关文章

springboot security使用jwt认证方式

《springbootsecurity使用jwt认证方式》:本文主要介绍springbootsecurity使用jwt认证方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录前言代码示例依赖定义mapper定义用户信息的实体beansecurity相关的类提供登录接口测试提供一

go中空接口的具体使用

《go中空接口的具体使用》空接口是一种特殊的接口类型,它不包含任何方法,本文主要介绍了go中空接口的具体使用,具有一定的参考价值,感兴趣的可以了解一下... 目录接口-空接口1. 什么是空接口?2. 如何使用空接口?第一,第二,第三,3. 空接口几个要注意的坑坑1:坑2:坑3:接口-空接口1. 什么是空接

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

SpringCloud之LoadBalancer负载均衡服务调用过程

《SpringCloud之LoadBalancer负载均衡服务调用过程》:本文主要介绍SpringCloud之LoadBalancer负载均衡服务调用过程,具有很好的参考价值,希望对大家有所帮助,... 目录前言一、LoadBalancer是什么?二、使用步骤1、启动consul2、客户端加入依赖3、以服务

SpringSecurity 认证、注销、权限控制功能(注销、记住密码、自定义登入页)

《SpringSecurity认证、注销、权限控制功能(注销、记住密码、自定义登入页)》SpringSecurity是一个强大的Java框架,用于保护应用程序的安全性,它提供了一套全面的安全解决方案... 目录简介认识Spring Security“认证”(Authentication)“授权” (Auth

如何用java对接微信小程序下单后的发货接口

《如何用java对接微信小程序下单后的发货接口》:本文主要介绍在微信小程序后台实现发货通知的步骤,包括获取Access_token、使用RestTemplate调用发货接口、处理AccessTok... 目录配置参数 调用代码获取Access_token调用发货的接口类注意点总结配置参数 首先需要获取Ac

讯飞webapi语音识别接口调用示例代码(python)

《讯飞webapi语音识别接口调用示例代码(python)》:本文主要介绍如何使用Python3调用讯飞WebAPI语音识别接口,重点解决了在处理语音识别结果时判断是否为最后一帧的问题,通过运行代... 目录前言一、环境二、引入库三、代码实例四、运行结果五、总结前言基于python3 讯飞webAPI语音

MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析

《MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析》本文将详细讲解MyBatis-Plus中的lambdaUpdate用法,并提供丰富的案例来帮助读者更好地理解和应... 目录深入探索MyBATis-Plus中Service接口的lambdaUpdate用法及示例案例背景

一文详解kafka开启kerberos认证的完整步骤

《一文详解kafka开启kerberos认证的完整步骤》这篇文章主要为大家详细介绍了kafka开启kerberos认证的完整步骤,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、kerberos安装部署二、准备机器三、Kerberos Server 安装1、配置krb5.con

Git如何修改已提交人的用户名和邮箱

《Git如何修改已提交人的用户名和邮箱》文章介绍了如何修改Git已提交人的用户名和邮箱,包括注意事项和具体步骤,确保操作正确无误... 目录git修改已提交人的用户名和邮箱前言第一步第二步总结git修改已提交人的用户名和邮箱前言需注意以下两点内容:需要在顶层目录下(php就是 .git 文件夹所在的目