【WCF安全】使用X509证书自定义验证
接触WCF时间比较短,在项目中要使用X509证书,纠结好几天终于有了结论,因此为了方便日后查阅和园友交流特意单独将部分代码提出,并做以记录。
1.准备工作
制作X509证书,此处用到三个证书名称
导入证书步骤:
第一步:运行mmc 打开控制台,添加证书
将证书导入,再设置证书的读取权限
2.方便阅读下文,先展示下代码整体结构
3.编写服务代码(WcfSite工程下)
服务一:
1 public class HelloService : IHelloService 2 { 3 public string DoWork() 4 { 5 return "Hello"; 6 } 7 } 8 9 [ServiceContract] 10 public interface IHelloService 11 { 12 [OperationContract] 13 string DoWork(); 14 }
服务二:
1 public class HiService : IHiService 2 { 3 public string DoWork() 4 { 5 return "Hi"; 6 } 7 } 8 9 10 [ServiceContract] 11 public interface IHiService 12 { 13 [OperationContract] 14 string DoWork(); 15 }
服务三:
public class NiHaoService : INiHaoService { public string DoWork() { return "你好"; } } [ServiceContract] public interface INiHaoService { [OperationContract] string DoWork(); }
服务写完之后开始着手准备X509的自定义验证
此实例的验证逻辑如下:
第一步:验证客户端x509证书的有效性;
当客户端试图调用服务资源时,首先进入此处进行x509的验证
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.IdentityModel.Selectors; 6 using System.Security.Cryptography.X509Certificates; 7 using System.Xml; 8 using System.ServiceModel; 9 10 namespace WcfSite.Code 11 { 12 /// <summary> 13 /// 验证X509证书 14 /// </summary> 15 public class X509Validator : System.IdentityModel.Selectors.X509CertificateValidator 16 { 17 /// <summary> 18 /// 日志路径 19 /// </summary> 20 private const string logPath = @"\Safety"; 21 /// <summary> 22 /// CA序列号集合Key=CA,Value=SN 23 /// </summary> 24 private static Dictionary<string, string> SNList = new Dictionary<string, string>(); 25 private static Object objlock = new object(); 26 27 #region X509CertificateValidator重写 28 /// <summary> 29 /// 验证X509证书 30 /// </summary> 31 /// <param name="certificate"></param> 32 public override void Validate(X509Certificate2 certificate) 33 { 34 if (certificate == null) 35 { 36 throw new FaultException("请安装X509证书"); 37 } 38 if (SNList.Count == 0) 39 { 40 GetX509SerialNumberList(); 41 } 42 lock (objlock) 43 { 44 if (!SNList.ContainsKey(certificate.Subject) || !SNList.ContainsValue(certificate.SerialNumber.ToUpper())) 45 { 46 throw new FaultException("X509证书无效"); 47 } 48 } 49 } 50 51 #endregion 52 53 #region 私有方法 54 /// <summary> 55 /// 获取x509证书序列号 56 /// </summary> 57 private void GetX509SerialNumberList() 58 { 59 XmlDocument doc = new XmlDocument(); 60 doc.Load(AppDomain.CurrentDomain.BaseDirectory + "X509SerialNumbers.xml"); 61 XmlNodeList nodes = doc.SelectNodes("X509SN/SerialNumbers/Number"); 62 foreach (XmlNode node in nodes) 63 { 64 string ca = String.Empty;//CA名称 65 string sn = String.Empty;//CA序列号 66 foreach (XmlAttribute xa in node.Attributes)//校验用户名密码 67 { 68 if (xa.Name == "CA") 69 ca = xa.Value; 70 else if (xa.Name == "SN") 71 sn = xa.Value; 72 } 73 if (!String.IsNullOrEmpty(ca) && !String.IsNullOrEmpty(sn)) 74 SNList.Add(ca, sn.ToUpper()); 75 } 76 } 77 #endregion 78 } 79 }
第二步:验证客户端是否有权限调用将要调用的服务资源
验证客户端使用的x509证书是否有权限调用服务资源
1 /// <summary> 2 /// 提供对服务操作的授权访问检查 3 /// </summary> 4 public class CustomServiceAuthorizationManager : System.ServiceModel.ServiceAuthorizationManager 5 { 6 /// <summary> 7 /// 日志路径 8 /// </summary> 9 private const string logPath = @"\Safety"; 10 11 #region ServiceAuthorizationManager重写 12 13 /// <summary> 14 /// 检查授权 15 /// </summary> 16 /// <param name="operationContext"></param> 17 /// <returns></returns> 18 protected override bool CheckAccessCore(OperationContext operationContext) 19 { 20 //请求调用的资源url 21 string action = operationContext.RequestContext.RequestMessage.Headers.Action; 22 Console.ForegroundColor = ConsoleColor.Red; 23 Console.ForegroundColor = ConsoleColor.White; 24 //ClaimSet 表示与某个实体关联的声明的集合。 25 //获取与授权策略关联的声明集 26 foreach (System.IdentityModel.Claims.ClaimSet cs in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets) 27 { 28 if (cs.Issuer == System.IdentityModel.Claims.ClaimSet.System) 29 { 30 foreach (System.IdentityModel.Claims.Claim claim in cs.FindClaims("http://tempuri.org/", System.IdentityModel.Claims.Rights.PossessProperty)) 31 { 32 //校验是否有调用权限 33 if (claim.Resource.ToString() == action) 34 { 35 return true;//通过 36 } 37 else 38 { 39 string url = action.Substring(0, action.LastIndexOf('/')); 40 if (claim.Resource.ToString() == url + "/all")//可以调用该服务下所有的方法 41 return true; 42 } 43 44 } 45 } 46 } 47 return false;//不通过 48 } 49 50 #endregion 51 }
获取服务端定义的资源权限集合
1 /// <summary> 2 /// 查询用户可调用的资源 3 /// 定义一组用于对用户进行授权的规则 4 /// </summary> 5 public class CustomAuthorizationPolicy : System.IdentityModel.Policy.IAuthorizationPolicy 6 { 7 /// <summary> 8 /// 证书名称(角色)-资源集合 9 /// </summary> 10 private static Dictionary<string, List<string>> dicRoleResources = new Dictionary<string, List<string>>(); 11 private static Object objlock = new object(); 12 /// <summary> 13 /// 日志路径 14 /// </summary> 15 private const string logPath = @"\Safety"; 16 string id = string.Empty; 17 public CustomAuthorizationPolicy() 18 { 19 id = new Guid().ToString(); 20 } 21 public System.IdentityModel.Claims.ClaimSet Issuer 22 { 23 get { return System.IdentityModel.Claims.ClaimSet.System; } 24 } 25 public string Id 26 { 27 get { return id; } 28 } 29 30 #region IAuthorizationPolicy方法实现 31 32 /// <summary> 33 /// 查询用户可调用的资源 34 /// </summary> 35 /// <param name="evaluationContext"></param> 36 /// <param name="state"></param> 37 /// <returns></returns> 38 public bool Evaluate(System.IdentityModel.Policy.EvaluationContext evaluationContext, ref object state) 39 { 40 bool flag = false; 41 bool r_state = false; 42 if (state == null) { state = r_state; } else { r_state = Convert.ToBoolean(state); } 43 if (!r_state) 44 { 45 List<System.IdentityModel.Claims.Claim> claims = new List<System.IdentityModel.Claims.Claim>(); 46 foreach (System.IdentityModel.Claims.ClaimSet cs in evaluationContext.ClaimSets) 47 { 48 foreach (System.IdentityModel.Claims.Claim claim in cs.FindClaims 49 (System.IdentityModel.Claims.ClaimTypes.Name, System.IdentityModel.Claims.Rights.PossessProperty)) 50 { 51 IEnumerable<string> resourceList = HelpGetServiceResourceByName(claim.Resource.ToString()); 52 foreach (string str in resourceList) 53 { 54 //授权的资源 55 claims.Add(new System.IdentityModel.Claims.Claim("http://tempuri.org/", str, System.IdentityModel.Claims.Rights.PossessProperty)); 56 } 57 } 58 } 59 evaluationContext.AddClaimSet(this, new System.IdentityModel.Claims.DefaultClaimSet(Issuer, claims)); r_state = true; flag = true; 60 } 61 else 62 { 63 flag = true; 64 } 65 return flag; 66 } 67 68 #endregion 69 70 #region 私有方法 71 72 /// <summary> 73 /// 通过证书名称(角色)获取资源 74 /// </summary> 75 /// <param name="caRole">证书名称(角色)</param> 76 /// <returns></returns> 77 private IEnumerable<string> HelpGetServiceResourceByName(string caRole) 78 { 79 if (dicRoleResources.Count == 0) 80 { 81 lock (objlock) 82 { 83 GetRoleResourceList(); 84 } 85 } 86 return dicRoleResources[caRole]; 87 } 88 /// <summary> 89 /// 读取所有证书名称(角色)的可访问资源 90 /// </summary> 91 private void GetRoleResourceList() 92 { 93 XmlDocument doc = new XmlDocument(); 94 doc.Load(AppDomain.CurrentDomain.BaseDirectory + "RoleResourceConfig.xml"); 95 XmlNodeList nodes = doc.SelectNodes("ResourceConfig/Role"); 96 foreach (XmlNode node in nodes) 97 { 98 foreach (XmlAttribute xa in node.Attributes) 99 { 100 if (xa.Name == "Name") //查询角色下的所有资源 101 { 102 List<string> lists = new List<string>(); //资源集合 103 foreach (XmlNode xn in node.ChildNodes) 104 { 105 if (xn.Name == "Resource") 106 { 107 lists.Add(xn.InnerXml); 108 } 109 } 110 if (!dicRoleResources.ContainsKey(xa.Value)) 111 dicRoleResources.Add(xa.Value, lists); 112 } 113 } 114 } 115 } 116 #endregion 117 118 }
到此,所有服务方法和x509自定义验证代码都已完成。之后将要在配置文件中配置客户端x509的权限信息
首先配置客户端x509证书的序列号,因为此实例是通过序列号验证证书是否有效的(X509SerialNumbers.xml)
1 <?xml version="1.0" encoding="utf-8" ?> 2 <X509SN> 3 <SerialNumbers> 4 <Number CA="CN=Test01CA" SN="0e9a8c9d238597ae47ef56eb7e3a0b61"/> 5 </SerialNumbers> 6 </X509SN>
然后配置客户端x509证书能访问的服务资源权限(RoleResourceConfig.xml)
1 <?xml version="1.0" encoding="utf-8" ?> 2 <ResourceConfig> 3 <!--TEST1--> 4 <Role Name="Test01CA"> 5 <!--格式:地址+方法名;all表示有权限访问该地址下所有的服务方法--> 6 <Resource>http://tempuri.org/IHiService/all</Resource> 7 <!--格式:地址+方法名;all表示有权限访问该地址下所有的服务方法--> 8 <Resource>http://tempuri.org/IHelloService/all</Resource> 9 </Role> 10 <!--TEST2--> 11 <Role Name="Test02CA"> 12 <!--格式:地址+方法名;all表示有权限访问该地址下所有的服务方法--> 13 <Resource>http://tempuri.org/INiHaoService/all</Resource> 14 </Role> 15 </ResourceConfig>
最后一项,配置服务传说中的ABC
直接上web.config
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> <system.serviceModel> <services> <service name="WcfSite.HiService" behaviorConfiguration="httpBehavior"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsBinding" contract="WcfSite.IHiService"> <identity> <dns value="localhost" /> </identity> </endpoint> <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" /> <host> <baseAddresses> <add baseAddress="http://localhost:9903/HiService" /> </baseAddresses> </host> </service> <service name="WcfSite.HelloService" behaviorConfiguration="httpBehavior"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsBinding" contract="WcfSite.IHelloService"> <identity> <dns value="localhost" /> </identity> </endpoint> <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" /> <host> <baseAddresses> <add baseAddress="http://localhost:9903/HelloService" /> </baseAddresses> </host> </service> <service name="WcfSite.NiHaoService" behaviorConfiguration="httpBehavior"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsBinding" contract="WcfSite.INiHaoService"> <identity> <dns value="localhost" /> </identity> </endpoint> <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" /> <host> <baseAddresses> <add baseAddress="http://localhost:9903/NiHaoService" /> </baseAddresses> </host> </service> </services> <bindings> <wsHttpBinding> <binding name="wsBinding" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="01:00:00" sendTimeout="01:00:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="999999999" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"> <readerQuotas maxDepth="32" maxStringContentLength="900000000" maxArrayLength="163840" maxBytesPerRead="40960" maxNameTableCharCount="163840" /> <reliableSession inactivityTimeout="01:00:00"/> <security mode="Message"> <!--定义消息级安全性要求的类型,为证书--> <message clientCredentialType="Certificate" /> </security> </binding> </wsHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="httpBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceCredentials> <serviceCertificate findValue="TestServiceCA" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" /> <clientCertificate> <!--自定义对客户端进行证书认证方式 这里为 None--> <authentication certificateValidationMode="Custom" customCertificateValidatorType="WcfSite.Code.X509Validator,WcfSite"/> </clientCertificate> </serviceCredentials> <serviceAuthorization serviceAuthorizationManagerType="WcfSite.Code.CustomServiceAuthorizationManager,WcfSite"> <authorizationPolicies> <add policyType="WcfSite.Code.CustomAuthorizationPolicy,WcfSite"/> </authorizationPolicies> </serviceAuthorization> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> </system.serviceModel> </configuration>
到此编写服务端代码完成。
继续客户端的,比较简单:
直接引用服务,修改一下配置
先上客户端config代码
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <bindings> 5 <wsHttpBinding> 6 <binding name="wsBinding_Test" closeTimeout="00:10:00" openTimeout="00:10:00" 7 receiveTimeout="01:00:00" sendTimeout="01:00:00" allowCookies="false" bypassProxyOnLocal="false" 8 hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" 9 maxReceivedMessageSize="999999999" messageEncoding="Text" textEncoding="utf-8" 10 useDefaultWebProxy="true"> 11 <readerQuotas maxDepth="32" maxStringContentLength="999999999" maxArrayLength="16384" 12 maxBytesPerRead="4096" maxNameTableCharCount="16384" /> 13 <reliableSession inactivityTimeout="01:00:00"/> 14 <security> 15 <message clientCredentialType="Certificate" /> 16 </security> 17 </binding> 18 </wsHttpBinding> 19 </bindings> 20 <behaviors> 21 <endpointBehaviors> 22 <behavior name="myClientBehavior"> 23 <clientCredentials> 24 <!--客户端证书--> 25 <clientCertificate findValue="Test01CA" storeName="My" storeLocation="LocalMachine" 26 x509FindType="FindBySubjectName"/> 27 <serviceCertificate> 28 <authentication certificateValidationMode="None"/> 29 </serviceCertificate> 30 </clientCredentials> 31 </behavior> 32 </endpointBehaviors> 33 </behaviors> 34 <client> 35 <endpoint address="http://localhost:9903/HiService.svc" binding="wsHttpBinding" behaviorConfiguration="myClientBehavior" 36 bindingConfiguration="wsBinding_Test" contract="_HiService.IHiService" 37 name="WSHttpBinding_IHiService"> 38 <identity> 39 <dns value="TestServiceCA" /> 40 </identity> 41 </endpoint> 42 <endpoint address="http://localhost:9903/NiHaoService.svc" binding="wsHttpBinding" behaviorConfiguration="myClientBehavior" 43 bindingConfiguration="wsBinding_Test" contract="_NiHaoService.INiHaoService" 44 name="WSHttpBinding_INiHaoService"> 45 <identity> 46 <dns value="TestServiceCA" /> 47 </identity> 48 </endpoint> 49 <endpoint address="http://localhost:9903/HelloService.svc" binding="wsHttpBinding" behaviorConfiguration="myClientBehavior" 50 bindingConfiguration="wsBinding_Test" contract="_HelloService.IHelloService" 51 name="WSHttpBinding_IHelloService"> 52 <identity> 53 <dns value="TestServiceCA" /> 54 </identity> 55 </endpoint> 56 </client> 57 </system.serviceModel> 58 </configuration>
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 try 6 { 7 _HelloService.HelloServiceClient hs = new _HelloService.HelloServiceClient(); 8 Console.WriteLine("HelloService:" + hs.DoWork()); 9 } 10 catch (Exception ex) 11 { 12 Console.WriteLine(ex.Message); 13 } 14 try 15 { 16 _HiService.HiServiceClient hs = new _HiService.HiServiceClient(); 17 Console.WriteLine("HiService:" + hs.DoWork()); 18 } 19 catch (Exception ex) 20 { 21 Console.WriteLine(ex.Message); 22 } 23 try 24 { 25 _NiHaoService.NiHaoServiceClient hs = new _NiHaoService.NiHaoServiceClient(); 26 Console.WriteLine("NiHaoService:" + hs.DoWork()); 27 } 28 catch (Exception ex) 29 { 30 Console.WriteLine(ex.Message); 31 } 32 Console.ReadLine(); 33 } 34 }
到这里 客户端和服务端的代码都算撸完了。
现在进入运行阶段
1.先将服务端启动,再启动客户端
2.出结果
。。。。。
源码下载 Wcfx509.rar