自定义实现wcf的用户名密码验证
目前wcf分为【传输层安全】【消息层安全】两种,本身也自带的用户名密码验证的功能,但是ms为了防止用户名密码明文在网络上传输,所以,强制要求一旦使用【用户名密码】校验功能,则必须使用证书,按照常理讲,这是对的,但是我们的环境特殊。由于处于各级的路由器之下,加上ssl的性能问题,我们通过统一的网关进行ssl处理,也就是说,客户端到路由之间走的是https,而路由到我们的服务器之间走的则是http,这样使得证书集中管理,性能也有所提升。但是,却用不了wcf自己的【用户名密码校验】功能。
经过在网上找寻资料以及参照各种代码,最终决定使用【behaviorExtensions】来解决这个问题,【behaviorExtensions】的好处是可以让我们实现与asp.net mvc类似的AOP功能,即面向切面,我们可以为wcf创建【Interpector】(mvc中的【filter】们)来对一个接口的方法被调用前后进行处理。我们的设计是在接口被调用前从【message】的【header】中取得我们事先在客户端写入的【用户名】【密码】,然后进行校验,如果通过,则继续执行,否则报错直接终止请求进程。
实现的代码网上有很多,这里我只为客户端封装了一个dll进行使用,而服务端我则是写死在代码中的,感觉没有必要。
服务器一共两个类,行为类【ServiceBehavior】,检测类【ServiceInterpector】,我们这样理解,【ServiceBehavior】是用来被wcf识别并且配置到具体的服务协定中的,因为它是一个【Behavior】,而【ServiceInterpector】则是在【ServiceBehavior】中被调用,执行Validate方法,对用户名和密码进行操作。
public class ServiceBehavior : BehaviorExtensionElement, IServiceBehavior
{
public override Type BehaviorType
{
get { return typeof(ServiceBehavior); }
}
protected override object CreateBehavior()
{
return new ServiceBehavior();
}
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher epDisp in chDisp.Endpoints)
{
epDisp.DispatchRuntime.MessageInspectors.Add(new ServiceInterpector());
}
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion
}
上面这个【ServiceBehavior】完全通用,我也是在【网上】直接down下来的,比较重点的就是里面的
epDisp.DispatchRuntime.MessageInspectors.Add(new ServiceInterpector());
这一行了,这一行比较特特,它在这里引用了我们另一个类【ServiceInterpector】,两个类的关系只有这一个地方。ServiceInterpector的代码如下:
public class ServiceInterpector : IDispatchMessageInspector
{
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
Console.WriteLine(request); var username = GetHeaderValue("OperationUserName");
var password = GetHeaderValue("OperationPassword");
var validcode = GetHeaderValue("OperationValidCode");
if(!string.IsNullOrEmpty(request.Headers.Action))
{
if (!B_UserValidate.Validate(username, password))
{
throw new MsgException("非法的用户名与密码!");
}
}
return null;//if success return null.
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
private string GetHeaderValue(string name, string ns = "http://tempuri.org")
{
var headers = OperationContext.Current.IncomingMessageHeaders;
var index = headers.FindHeader(name, ns);
if (index > -)
return headers.GetHeader<string>(index);
else
return null;
}
#endregion
}
注意上面【B_UserValidate.Validate(username, password)】这句代码,是用来判断用户名和密码是否合法的,至于【OperationValidCode】这个东西,取出来是为以后使用的,这个地方并没有起到任何作用。这就是服务端,其实非常简单,因为一个服务做一个就可以了,也不存在不同的验证体系的问题。
但是,客户端就悲剧了,因为客户端有可能同时引用多个wcf,而这多个wcf都使用我上面这种用户名密码验证,而客户端的开发人员又不想在开发的时候每次调用前都带上【用户名】和【密码】,一次设定多次使用是必须的要求,所以,只能使用字典来实现。这里我们用了一个类,如下:
public static class UserModelStatic
{
private static Dictionary<string, string[]> dicUserName = new Dictionary<string,string[]>();
/// <summary>
/// 设置用户名及密码,在用户调用对应的WCF地址时,将会使用此用户名密码
/// 如果重复设置,最新的将会覆盖旧的
/// </summary>
/// <param name="Address">wcf的地址,例如: http://www.test.com/myservice.svc</param>
/// <param name="username">用户名</param>
/// <param name="passsword">密码</param>
public static void SetUsernamePassword(string Address,string username,string passsword)
{
if(Address==null || username==null || passsword==null)
{
throw new Exception("用户名或密码或wcf地址为空");
}
Address = Address.ToUpper();
string[] up = new string[]
{
username,passsword
};
if(!dicUserName.ContainsKey(Address))
{
dicUserName.Add(Address, up);
}
else
{
dicUserName[Address] = up;
}
} /// <summary>
/// 设置用户名及密码,如果用户调用某一wcf但是没有设置此wcf地址设定的用户名密码,则会默认使用此处设置的用户名和密码
/// 如果重复设置,最新的将会覆盖旧的
/// </summary>
/// <param name="username">用户名</param>
/// <param name="passsword">密码</param>
public static void SetUsernamePassword(string username,string password)
{
UserNamePasswordDefault = new string[] { username, password };
}
/// <summary>
/// 获取用户名和密码
/// </summary>
/// <returns>{"用户名","密码"}</returns>
public static string[] GetUserPassword()
{
return UserNamePasswordDefault;
}
/// <summary>
/// 获取用户名和密码
/// </summary>
/// <returns>{"用户名","密码"}</returns>
/// <param name="Address">wcf接口的地址,例如: http://www.test.com/myservice.svc </param>
public static string[] GetUserPassword(string Address)
{
Address = Address.ToUpper();
if(!dicUserName.ContainsKey(Address))
{
return null;
}
else
{
return dicUserName[Address];
}
}
private static string[] UserNamePasswordDefault = null;
}
这个类用于全局保存【用户名】和【密码】,因为我所面向的是直接在vs里面生成服务引用代码的同学们,但是,我却没能在客户端的【Interpector】找到取得app.config中服务协定的名称的方法。所以,也只能根据请求的地址来区分。
上面的类提供了两种设定密码的方式,一种是带【地址】一种是不带。客户端调用wcf时会先根本调用的地方去取用户名和密码,如果没取到,则会使用那个唯一一个不带【地址】的公共【用户名】【密码】,如果还是取不到,则进抛出异常。抛出异常的目的是为了在测试的时候发现问题,而且强制一旦配置了一个引用使用这种用户名密码验证的行为就被对用户名和密码进行设定——哪怕是错的。
客户端一共有三个类,除了上面这种,以及与服务端一样的【ServiceBehavior】类(代码在上文中),就只有一个【Interpector】不同,这个【Interpector】处理了客户端所有的关于【用户名密码】登陆的逻辑,代码如下:
public class UserNameValidateClientInterpector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
string wcfAddress = channel.Via.ToString();
string[] up = UserModelStatic.GetUserPassword(wcfAddress);
if (up == null)
{
up = UserModelStatic.GetUserPassword();
}
if (up == null)
{
throw new Exception("您的验证信息尚未填写,请填写后市调用WCF");
}
var userNameHeader = MessageHeader.CreateHeader("OperationUserName", "http://tempuri.org", up[], false, "");
var passwordHeader = MessageHeader.CreateHeader("OperationPassword", "http://tempuri.org", up[], false, "");
request.Headers.Add(userNameHeader);
request.Headers.Add(passwordHeader);
Console.WriteLine(request);
return null;
}
}
这样,一切就都OK了,将客户端的三个类封装在一个单独的DLL中,将服务端的两类写在服务端的项目中,重点在下面,我们需要进行配置了。
服务端的配置
服务的配置有这样几个目的
首先,你要让wcf能找到你这个behavior,配置如下:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="UserNameValidateServiceBehavior" type="TestWcf.ServiceBehavior, TestWcf, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
虽然写在同一个项目中,但是它却无法自己找到,这个很难过,只能配置了,而且还有加上type来配置,之所以放在wcf项目中也是因为这个type。type是一个用,隔开的字符串,一共五段,第一段是你这人Behavior类的【强命名】(强命名就是 命名空间+类名),第二段是你这个Behavior所在的程序集名称,一般就是你的项目名,也就是你的项目生成的dll或者exe的名称(注意不带.dll和.exe),第三段则是版本号,这个版本号可以在你的项目的Properties里面的Assembly.cs里面找到,截图如下:

再信下就是程序集签名了,我们目前只有在特殊的情况下才对程序集进行签名来控制版本,而这个项目我们没有签,所以直接写null就可以了,想看自己有没有签,只需要在项目的属性里面找到【签名】这一标签页

这个如果选中了,就说明签名了,这时候你会有一串字符串,填在上面最后一段即可。
就这样,我们就完成了服务端的第一个配置。
其次,你要让你的这个程序集成为一个真的Behavior
这句话的意思是,我们在第一步中的操作只是引用了这个类,但是却没有给这个类应有的身份,所以,我们需要一个Behavior来使用它。配置如下:
<behaviors>
<serviceBehaviors>
<behavior name="ServiceInterpectorBehavior">
<dataContractSerializer maxItemsInObjectGraph="2147483646" />
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="20" maxConcurrentSessions="20"
maxConcurrentInstances="20" />
<UserNameValidateServiceBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
有人问我,为何这么多,其实只有UserNameValidateServiceBehavior这一个有用,所以,让我们来精简一下:
<behaviors>
<serviceBehaviors>
<behavior name="ServiceInterpectorBehavior">
<UserNameValidateServiceBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
来解释一下,上面配置的含义,<behavior name=,这个东西,这个name是自己给这个新的behavior起的名字,在下面的地方需要使用。<UserNameValidateServiceBehavior /> 这个东西是重要,原因是它就是我们上面引用的我们的【ServiceBehavior】类,回到第一步配置的图,里面可以看见我们引用的时候给那个引用的节点起了个名字,就叫“UserNameValidateServiceBehavior ”,所以,在这里直接以节点的形势将它加给behavior就可以了。
第三步,就是配置服务端的service
在你需要使用【用户名密码验证】的服务的service节点加上 behaviorConfiguration="ServiceInterpectorBehavior",就可以了。
客户端的配置
客户端和配置和服务端一样,只是第三步配置的不是service,而是client节点。
另外一定要确保dll被客户端引用并且属性里面设置复制到本地,然后它的type设定的时候,直接在dll生成的项目里面看就可以了。
客户端的使用
客户端在使用的时候,问题就不大了,在你调用需要【用户名】【密码】验证的服务之前,请配置你的【UserModelStatic】中的用户名和密码。比如【WcfClientInterpector】是我的dll。那么我在Program.cs里面是这样配置的
WcfClientInterpector.UserModelStatic.SetUsernamePassword(“http://localhost:3868/TestWcf.svc”, "ensleep", "password");
这样,你后面凡是调用【http://localhost:3868/TestWcf.svc】这个地址的wcf服务时,都会带上你设置的【用户名】和【密码】,假如你的服务比如多,但是【用户名】【密码】都一样,你可以这样设置:
WcfClientInterpector.UserModelStatic.SetUsernamePassword( "ensleep", "password");
这种情况下,如果系统没找到你请求的wcf服务的地址对应的用户名和密码,则会使用你设定的这处公用的【用户名和密码】。
至于其它的,已经ok了,以前该怎么使用wcf就怎么使用。
自定义实现wcf的用户名密码验证的更多相关文章
- WCF用户名密码验证方式
WCF使用用户名密码验证 服务契约 namespace WCFUserNameConstract { [ServiceContract] public interface IWcfContract { ...
- WCF 安全性之 自定义用户名密码验证
案例下载 http://download.csdn.net/detail/woxpp/4113172 客户端调用代码 通过代理类 代理生成 参见 http://www.cnblogs.com/woxp ...
- 【WCF】Silverlight+wcf+自定义用户名密码验证
本文摘自 http://www.cnblogs.com/virusswb/archive/2010/01/26/1656543.html 在昨天的博文Silverlight3+wcf+在不使用证书的情 ...
- 【WCF】使用“用户名/密码”验证的合理方法
我不敢说俺的方法是最佳方案,反正这世界上很多东西都是变动的,正像老子所说的——“反(返)者,道之动”.以往看到有些文章中说,为每个客户端安装证书嫌麻烦,就直接采用把用户名和密码塞在SOAP头中发送,然 ...
- WCF服务安全控制之netTcpBinding的用户名密码验证【转】
选择netTcpBinding WCF的绑定方式比较多,常用的大体有四种: wsHttpBinding basicHttpBinding netTcpBinding wsDualHttpBinding ...
- WCF的用户名+密码认证方式(转)
概述 今天在做Master Data Service(后面简称MDS)项目时需要通过WCF来使用MDS的API,从而对MDS的数据进行操作.在这个过程中,遇到了一个棘手的问题,就是在客户端调用Web ...
- OpenVPN使用用户名/密码验证方式
OpenVPN推荐使用证书进行认证,安全性很高,但是配置起来很麻烦.还好它也能像pptp等vpn一样使用用户名/密码进行认证. 不管何种认证方式,服务端的ca.crt, server.crt, ser ...
- WebService 用户名密码验证
原文:WebService 用户名密码验证 在项目开发的过程中,WebService是经常要用的,当调用WebService方法时,需要经过服务的验证才可以调用,一般就是用户名/密码验证,还有一个就是 ...
- Python实现LDAP用户名密码验证
网上借鉴了不少东西,下面是python代码,备份后用. 思路,因为每个用户的组都不一样,这样就导致了dn不一致的情况, 据需要先根据用户名获取该用户的dn,然后再bind用户名和密码进行验证. 反正是 ...
随机推荐
- EF4.4 升级EF6.0问题总结
如出现下面代码错误,基本可能确定EF数据库配置错误 在 System.Data.Entity.Core.Metadata.Edm.MetadataArtifactLoaderCompositeReso ...
- 深入理解Vue父子组件通讯的属性和事件
在html中使用元素,会有一些属性,如class,id,还可以绑定事件,自定义组件也是可以的.当在一个组件中,使用了其他自定义组件时,就会利用子组件的属性和事件来和父组件进行数据交流. 父子组件之间的 ...
- elasticsearch term 查询之一
1.前言 term级别查询将按照存储在倒排索引中的确切字词进行操作,这些查询通常用于数字,日期和枚举等结构化数据,而不是全文本字段. 或者,它们允许您制作低级查询,并在分析过程之前进行. term级别 ...
- android中实现内容搜索
在编写android搜索代码的时候,怎样去实现搜索功能,考虑中的有两种: 自己定义搜索方法: 1.自己定义搜索输入框,搜索图标,搜索button 2.自己定义语音输入方法 3.自己定义经常使用热词内容 ...
- Java虚拟机中的栈和堆的定义和区别
在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配. 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配 ...
- 转:TensorFlow和Caffe、MXNet、Keras等其他深度学习框架的对比
http://geek.csdn.net/news/detail/138968 Google近日发布了TensorFlow 1.0候选版,这第一个稳定版将是深度学习框架发展中的里程碑的一步.自Tens ...
- 倍福TwinCAT(贝福Beckhoff)应用教程11.1 TwinCAT应用小程序1 如何读写数字量模拟量输入输出(DI,DO,AI,AO)
常见的模拟量模块(还有更高端和更低端的,使用方法都一样) EL3054和EL4024(4路模拟量输入和输出模块) 常见的数字量模块(还有更高端和更低端的,使用方法都一样) EL1809和EL280 ...
- 让Qt Creator支持Windows Phone 8开发
让Qt Creator支持Windows Phone 8开发 近期QtCreator3.2出了.修复了一些Bug.比上一个版本号3.1.2要好了一些. 因为在上一个版本号(Qt for WinRT自带 ...
- MPTCP 源码分析(七) 拥塞控制
简述 MPTCP的拥塞控制对TCP的拥塞控制的线性增加阶段进行了修改,而慢启动,快速重传. 快速恢复都没有改变.每条子路径拥有自己的cwnd,MPTCP的拥塞算法主要关心cwnd的改变. ...
- 什么是Pro*C/C++,嵌入式SQL,第一个pro*c程序,pro*c++,Makefile,Proc增删改查
1 什么是Pro*C/C++ 1.通过在过程编程语言C/C++中嵌入SQL语句而开发出的应用程序 2.什么是嵌入式SQL 1.在通用编程语言中使用的SQL称为嵌入式SQL 2.在SQL标准中定义 ...