【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

posted @ 2015-04-01 16:09  J.Y  阅读(1963)  评论(0编辑  收藏  举报