【WCF安全】使用X509证书自定义验证
2024-09-04 15:02:46
接触WCF时间比较短,在项目中要使用X509证书,纠结好几天终于有了结论,因此为了方便日后查阅和园友交流特意单独将部分代码提出,并做以记录。
1.准备工作
制作X509证书,此处用到三个证书名称
导入证书步骤:
第一步:运行mmc 打开控制台,添加证书
将证书导入,再设置证书的读取权限
2.方便阅读下文,先展示下代码整体结构
3.编写服务代码(WcfSite工程下)
服务一:
public class HelloService : IHelloService
{
public string DoWork()
{
return "Hello";
}
} [ServiceContract]
public interface IHelloService
{
[OperationContract]
string DoWork();
}
服务二:
public class HiService : IHiService
{
public string DoWork()
{
return "Hi";
}
} [ServiceContract]
public interface IHiService
{
[OperationContract]
string DoWork();
}
服务三:
public class NiHaoService : INiHaoService
{
public string DoWork()
{
return "你好";
}
} [ServiceContract]
public interface INiHaoService
{
[OperationContract]
string DoWork();
}
服务写完之后开始着手准备X509的自定义验证
此实例的验证逻辑如下:
第一步:验证客户端x509证书的有效性;
当客户端试图调用服务资源时,首先进入此处进行x509的验证
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IdentityModel.Selectors;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.ServiceModel; namespace WcfSite.Code
{
/// <summary>
/// 验证X509证书
/// </summary>
public class X509Validator : System.IdentityModel.Selectors.X509CertificateValidator
{
/// <summary>
/// 日志路径
/// </summary>
private const string logPath = @"\Safety";
/// <summary>
/// CA序列号集合Key=CA,Value=SN
/// </summary>
private static Dictionary<string, string> SNList = new Dictionary<string, string>();
private static Object objlock = new object(); #region X509CertificateValidator重写
/// <summary>
/// 验证X509证书
/// </summary>
/// <param name="certificate"></param>
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null)
{
throw new FaultException("请安装X509证书");
}
if (SNList.Count == )
{
GetX509SerialNumberList();
}
lock (objlock)
{
if (!SNList.ContainsKey(certificate.Subject) || !SNList.ContainsValue(certificate.SerialNumber.ToUpper()))
{
throw new FaultException("X509证书无效");
}
}
} #endregion #region 私有方法
/// <summary>
/// 获取x509证书序列号
/// </summary>
private void GetX509SerialNumberList()
{
XmlDocument doc = new XmlDocument();
doc.Load(AppDomain.CurrentDomain.BaseDirectory + "X509SerialNumbers.xml");
XmlNodeList nodes = doc.SelectNodes("X509SN/SerialNumbers/Number");
foreach (XmlNode node in nodes)
{
string ca = String.Empty;//CA名称
string sn = String.Empty;//CA序列号
foreach (XmlAttribute xa in node.Attributes)//校验用户名密码
{
if (xa.Name == "CA")
ca = xa.Value;
else if (xa.Name == "SN")
sn = xa.Value;
}
if (!String.IsNullOrEmpty(ca) && !String.IsNullOrEmpty(sn))
SNList.Add(ca, sn.ToUpper());
}
}
#endregion
}
}
第二步:验证客户端是否有权限调用将要调用的服务资源
验证客户端使用的x509证书是否有权限调用服务资源
/// <summary>
/// 提供对服务操作的授权访问检查
/// </summary>
public class CustomServiceAuthorizationManager : System.ServiceModel.ServiceAuthorizationManager
{
/// <summary>
/// 日志路径
/// </summary>
private const string logPath = @"\Safety"; #region ServiceAuthorizationManager重写 /// <summary>
/// 检查授权
/// </summary>
/// <param name="operationContext"></param>
/// <returns></returns>
protected override bool CheckAccessCore(OperationContext operationContext)
{
//请求调用的资源url
string action = operationContext.RequestContext.RequestMessage.Headers.Action;
Console.ForegroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.White;
//ClaimSet 表示与某个实体关联的声明的集合。
//获取与授权策略关联的声明集
foreach (System.IdentityModel.Claims.ClaimSet cs in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
if (cs.Issuer == System.IdentityModel.Claims.ClaimSet.System)
{
foreach (System.IdentityModel.Claims.Claim claim in cs.FindClaims("http://tempuri.org/", System.IdentityModel.Claims.Rights.PossessProperty))
{
//校验是否有调用权限
if (claim.Resource.ToString() == action)
{
return true;//通过
}
else
{
string url = action.Substring(, action.LastIndexOf('/'));
if (claim.Resource.ToString() == url + "/all")//可以调用该服务下所有的方法
return true;
} }
}
}
return false;//不通过
} #endregion
}
获取服务端定义的资源权限集合
/// <summary>
/// 查询用户可调用的资源
/// 定义一组用于对用户进行授权的规则
/// </summary>
public class CustomAuthorizationPolicy : System.IdentityModel.Policy.IAuthorizationPolicy
{
/// <summary>
/// 证书名称(角色)-资源集合
/// </summary>
private static Dictionary<string, List<string>> dicRoleResources = new Dictionary<string, List<string>>();
private static Object objlock = new object();
/// <summary>
/// 日志路径
/// </summary>
private const string logPath = @"\Safety";
string id = string.Empty;
public CustomAuthorizationPolicy()
{
id = new Guid().ToString();
}
public System.IdentityModel.Claims.ClaimSet Issuer
{
get { return System.IdentityModel.Claims.ClaimSet.System; }
}
public string Id
{
get { return id; }
} #region IAuthorizationPolicy方法实现 /// <summary>
/// 查询用户可调用的资源
/// </summary>
/// <param name="evaluationContext"></param>
/// <param name="state"></param>
/// <returns></returns>
public bool Evaluate(System.IdentityModel.Policy.EvaluationContext evaluationContext, ref object state)
{
bool flag = false;
bool r_state = false;
if (state == null) { state = r_state; } else { r_state = Convert.ToBoolean(state); }
if (!r_state)
{
List<System.IdentityModel.Claims.Claim> claims = new List<System.IdentityModel.Claims.Claim>();
foreach (System.IdentityModel.Claims.ClaimSet cs in evaluationContext.ClaimSets)
{
foreach (System.IdentityModel.Claims.Claim claim in cs.FindClaims
(System.IdentityModel.Claims.ClaimTypes.Name, System.IdentityModel.Claims.Rights.PossessProperty))
{
IEnumerable<string> resourceList = HelpGetServiceResourceByName(claim.Resource.ToString());
foreach (string str in resourceList)
{
//授权的资源
claims.Add(new System.IdentityModel.Claims.Claim("http://tempuri.org/", str, System.IdentityModel.Claims.Rights.PossessProperty));
}
}
}
evaluationContext.AddClaimSet(this, new System.IdentityModel.Claims.DefaultClaimSet(Issuer, claims)); r_state = true; flag = true;
}
else
{
flag = true;
}
return flag;
} #endregion #region 私有方法 /// <summary>
/// 通过证书名称(角色)获取资源
/// </summary>
/// <param name="caRole">证书名称(角色)</param>
/// <returns></returns>
private IEnumerable<string> HelpGetServiceResourceByName(string caRole)
{
if (dicRoleResources.Count == )
{
lock (objlock)
{
GetRoleResourceList();
}
}
return dicRoleResources[caRole];
}
/// <summary>
/// 读取所有证书名称(角色)的可访问资源
/// </summary>
private void GetRoleResourceList()
{
XmlDocument doc = new XmlDocument();
doc.Load(AppDomain.CurrentDomain.BaseDirectory + "RoleResourceConfig.xml");
XmlNodeList nodes = doc.SelectNodes("ResourceConfig/Role");
foreach (XmlNode node in nodes)
{
foreach (XmlAttribute xa in node.Attributes)
{
if (xa.Name == "Name") //查询角色下的所有资源
{
List<string> lists = new List<string>(); //资源集合
foreach (XmlNode xn in node.ChildNodes)
{
if (xn.Name == "Resource")
{
lists.Add(xn.InnerXml);
}
}
if (!dicRoleResources.ContainsKey(xa.Value))
dicRoleResources.Add(xa.Value, lists);
}
}
}
}
#endregion }
到此,所有服务方法和x509自定义验证代码都已完成。之后将要在配置文件中配置客户端x509的权限信息
首先配置客户端x509证书的序列号,因为此实例是通过序列号验证证书是否有效的(X509SerialNumbers.xml)
<?xml version="1.0" encoding="utf-8" ?>
<X509SN>
<SerialNumbers>
<Number CA="CN=Test01CA" SN="0e9a8c9d238597ae47ef56eb7e3a0b61"/>
</SerialNumbers>
</X509SN>
然后配置客户端x509证书能访问的服务资源权限(RoleResourceConfig.xml)
<?xml version="1.0" encoding="utf-8" ?>
<ResourceConfig>
<!--TEST1-->
<Role Name="Test01CA">
<!--格式:地址+方法名;all表示有权限访问该地址下所有的服务方法-->
<Resource>http://tempuri.org/IHiService/all</Resource>
<!--格式:地址+方法名;all表示有权限访问该地址下所有的服务方法-->
<Resource>http://tempuri.org/IHelloService/all</Resource>
</Role>
<!--TEST2-->
<Role Name="Test02CA">
<!--格式:地址+方法名;all表示有权限访问该地址下所有的服务方法-->
<Resource>http://tempuri.org/INiHaoService/all</Resource>
</Role>
</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="" maxReceivedMessageSize=""
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true">
<readerQuotas maxDepth="" maxStringContentLength="" maxArrayLength=""
maxBytesPerRead="" maxNameTableCharCount="" />
<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代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsBinding_Test" closeTimeout="00:10:00" openTimeout="00:10:00"
receiveTimeout="01:00:00" sendTimeout="01:00:00" allowCookies="false" bypassProxyOnLocal="false"
hostNameComparisonMode="StrongWildcard" maxBufferPoolSize=""
maxReceivedMessageSize="" messageEncoding="Text" textEncoding="utf-8"
useDefaultWebProxy="true">
<readerQuotas maxDepth="" maxStringContentLength="" maxArrayLength=""
maxBytesPerRead="" maxNameTableCharCount="" />
<reliableSession inactivityTimeout="01:00:00"/>
<security>
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="myClientBehavior">
<clientCredentials>
<!--客户端证书-->
<clientCertificate findValue="Test01CA" storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectName"/>
<serviceCertificate>
<authentication certificateValidationMode="None"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://localhost:9903/HiService.svc" binding="wsHttpBinding" behaviorConfiguration="myClientBehavior"
bindingConfiguration="wsBinding_Test" contract="_HiService.IHiService"
name="WSHttpBinding_IHiService">
<identity>
<dns value="TestServiceCA" />
</identity>
</endpoint>
<endpoint address="http://localhost:9903/NiHaoService.svc" binding="wsHttpBinding" behaviorConfiguration="myClientBehavior"
bindingConfiguration="wsBinding_Test" contract="_NiHaoService.INiHaoService"
name="WSHttpBinding_INiHaoService">
<identity>
<dns value="TestServiceCA" />
</identity>
</endpoint>
<endpoint address="http://localhost:9903/HelloService.svc" binding="wsHttpBinding" behaviorConfiguration="myClientBehavior"
bindingConfiguration="wsBinding_Test" contract="_HelloService.IHelloService"
name="WSHttpBinding_IHelloService">
<identity>
<dns value="TestServiceCA" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
class Program
{
static void Main(string[] args)
{
try
{
_HelloService.HelloServiceClient hs = new _HelloService.HelloServiceClient();
Console.WriteLine("HelloService:" + hs.DoWork());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
try
{
_HiService.HiServiceClient hs = new _HiService.HiServiceClient();
Console.WriteLine("HiService:" + hs.DoWork());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
try
{
_NiHaoService.NiHaoServiceClient hs = new _NiHaoService.NiHaoServiceClient();
Console.WriteLine("NiHaoService:" + hs.DoWork());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
到这里 客户端和服务端的代码都算撸完了。
现在进入运行阶段
1.先将服务端启动,再启动客户端
2.出结果
。。。。。
源码下载 Wcfx509.rar
最新文章
- JAVA 设计模式 享元模式
- kill 根据PID终止进程
- [poj2184]我是来水一下背包的
- Minimum Size Subarray Sum
- phpcms筛选功能
- linux mysql服务器迁移
- Unity帧序列实时渲染脚本
- (转)html5开发之viewport使用
- Apache CXF 101 Win Eclipse开发环境搭建
- python打包成exe
- 兼容 console 没删除引起 低级浏览器 报错问题
- 原生jdbc操作mysql数据库详解
- iOS开发基础-图片切换(3)之属性列表
- Tkinter 项目-屏保
- 简单的proxy之TinyHTTPProxy.py
- Shell教程 之流程控制
- (转)python中的selectors模块
- 显示eclipse中Problem窗口的方法
- jQuery基础笔记(5)
- psutil-3.4.2才是我的老系统(Windows XP)的菜
热门文章
- C# ---sender
- String创建方式的区别
- ()IT 职场经验)一位10年Java工作经验的架构师的经验分享,感觉很受用。
- .NET中如何测试Private和Protected方法
- Spring -- 入门,装备集合,自动装配,分散装配,自定义编辑器
- java8 函数接口 Predicate例子
- Spark- SparkSQL中 Row.getLong 出现NullPointerException错误的处理方法
- 初试Orchard Core CMS
- Django进阶Model篇003 - 数据库同步技巧
- mysql数据库(三):查询的其他用法