zoukankan      html  css  js  c++  java
  • 【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

  • 相关阅读:
    spark-submit配置说明
    spark dataset join 使用方法java
    关于join算法的四篇文章
    IO负载高的来源定位
    MySQL执行SHOW STATUS查询服务器状态状态之Handler_read_* 详解
    5.6中使用字符串存放时间,导致隐式转换发生的记录
    Linux User's Manual IOSTAT
    【转】MegaSAS RAID卡 BBU Learn Cycle周期的影响
    mac下SSH很快被断开
    Java的正则表达式
  • 原文地址:https://www.cnblogs.com/yf2011/p/4384204.html
Copyright © 2011-2022 走看看