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

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

TP-Link PDDNS服将于务6月30日正式停运:用户需转向第三方DDNS服务

《TP-LinkPDDNS服将于务6月30日正式停运:用户需转向第三方DDNS服务》近期,路由器制造巨头普联(TP-Link)在用户群体中引发了一系列重要变动,上个月,公司发出了一则通知,明确要求所... 路由器厂商普联(TP-Link)上个月发布公告要求所有用户必须完成实名认证后才能继续使用普联提供的 D

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

MyBatis-Flex BaseMapper的接口基本用法小结

《MyBatis-FlexBaseMapper的接口基本用法小结》本文主要介绍了MyBatis-FlexBaseMapper的接口基本用法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具... 目录MyBATis-Flex简单介绍特性基础方法INSERT① insert② insertSelec

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

Idea实现接口的方法上无法添加@Override注解的解决方案

《Idea实现接口的方法上无法添加@Override注解的解决方案》文章介绍了在IDEA中实现接口方法时无法添加@Override注解的问题及其解决方法,主要步骤包括更改项目结构中的Languagel... 目录Idea实现接China编程口的方法上无法添加@javascriptOverride注解错误原因解决方

MySQL修改密码的四种实现方式

《MySQL修改密码的四种实现方式》文章主要介绍了如何使用命令行工具修改MySQL密码,包括使用`setpassword`命令和`mysqladmin`命令,此外,还详细描述了忘记密码时的处理方法,包... 目录mysql修改密码四种方式一、set password命令二、使用mysqladmin三、修改u

java如何通过Kerberos认证方式连接hive

《java如何通过Kerberos认证方式连接hive》该文主要介绍了如何在数据源管理功能中适配不同数据源(如MySQL、PostgreSQL和Hive),特别是如何在SpringBoot3框架下通过... 目录Java实现Kerberos认证主要方法依赖示例续期连接hive遇到的问题分析解决方式扩展思考总

微服务架构之使用RabbitMQ进行异步处理方式

《微服务架构之使用RabbitMQ进行异步处理方式》本文介绍了RabbitMQ的基本概念、异步调用处理逻辑、RabbitMQ的基本使用方法以及在SpringBoot项目中使用RabbitMQ解决高并发... 目录一.什么是RabbitMQ?二.异步调用处理逻辑:三.RabbitMQ的基本使用1.安装2.架构

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学