1. WS-Trust       安全令牌服务 (STS) 是基于 WS-Trust协议构建、签署和颁发安全令牌的服务组件,可处理不同类型凭据的身份验证。
    ​WS-Trust​​是WS-*规范族中的一员,也是OASIS其中的一项标准,专门处理有关安全tokens的发布,更新和验证,确保各方参与者的互操作处在一个可信任的安全数据交换环境中。
          从较高层次看,WS-Trust使用四种服务操作来描述一个约定:颁发、验证、续订和取消。客户端分别调用这些操作来请求安全令牌、验证安全令牌、续订已过期的安全令牌以及取消不应再继续使用的安全令牌。WS-Trust规范定义了每个操作的语法:
          请求安全性令牌时:使用 WS-Trust 规范中定义的<RequestSecurityToken> 消息进行请求的。
          返回安全性令牌时:使用 WS-Trust 规范中定义的<RequestSecurityTokenResponse> 消息进行返回的。
          本章只是通过代码实现STS的基本功能,后面会通过反编译截获生成的RST和RSTS消息文本,可以用来理解颁发的SAML令牌的结构,也可以对令牌进行本地持久化。
    2、Geneva框架
        Geneva 框架是.NET3.5基础上的,.NET4下发布了新的框架WIF。Geneva 框架可为开发人员提供相关工具来构建基于声明的应用程序和服务,还提供相关工具来构建自定义STS 和应用程序。使用 Geneva 框架构建自定义 STS,而无需编写用于公开 WS-Trust 终结点或构建包含声明的 SAML 令牌的所有探测功能
    3、整体流程
         利用Geneva实现整个过程的流程如下:
  1.       Client端向IP-STS端提供身份凭据,申请令牌;IP-STS颁发基于声明的令牌;Client端利用令牌访问RP端; RP端获得并信任IP-STS对客户端身份 的声明,达到联盟认证的目的

   4、STS Server端


         利用Geneva框架开发Server端和Client端非常便利。只要按需继承关键的基类,并实现关键的函数即可。  这里采用IIS托管的WCF服务来发布STS。  WCF页面:



1 
<%
@ServiceHost language
=
C# Factory
=
"
STS.Core.STSServiceHostFactory
"
Service
=
"
STS.Core.STSServiceConfiguration
"
%>


  

      为了自定义的需求,这里的工厂类和配置类都继承了Geneva框架的基类。

  1. Factory类和Configuration类

1 
using
Microsoft.IdentityModel.Protocols.WSTrust;

2

namespace
STS.Core

3
{

4

public

class
STSServiceHostFactory : WSTrustServiceHostFactory

5
{

6

public

override
ServiceHostBase CreateServiceHost(
string
constructorString, Uri[] baseAddresses)

7
{

8
ServiceHostBase serviceHost
=

base
.CreateServiceHost(constructorString, baseAddresses);

9

//
可以在这里通过代码增加配置项、终结点、服务行为等


10



11

return
serviceHost;

12
}

13

public

class
STSServiceConfiguration : SecurityTokenServiceConfiguration

14
{

15

public
STSServiceConfiguration () :
base
()

16
{

17

//
这里可以修改STS的一些默认配置,如令牌的生存期等

18


19

//
配置STS服务实现类


20


this
.SecurityTokenService
=

typeof
(STSService);

21

//
设置用STS服务证书做安全令牌的签名证书


22


this
.SigningCredentials
=

new
X509SigningCredentials(STSConfiguration.Certificate);

23
}

24
}

25
}

26


  1.  可以看到需要一个类STSService,而这个类正是实现WS-Trust的核心,我们也继承自Geneva框架的基类,只需要实现几个比较关键的函数,就可以方便的实现自定义STS服务:

          GetIssuerName():获取颁发者名称

          GetScope():获取颁发策略

          GetOutputClaimsIdentity():获取声明标识的集合,用来生成安全令牌主体

          令牌默认生存期是10个小时,如果需要修改令牌生存期,可以实现下面的函数

      GetTokenLifetime():获取令牌生存期

          令牌是加密的,如果需要公开一些信息,供客户端直接使用,可以实现下面的函数

          GetDisplayToken():获取展示令牌 

1 
using
Microsoft.IdentityModel.Protocols.WSTrust;

2

namespace
STS.Core

3
{

4

public

class
STSService : SecurityTokenService

5
{

6

public
STSService(SecurityTokenServiceConfiguration configuration)

7
:
base
(configuration)

8
{

9
}

10


11

///

<summary>
返回颁发者名称
</summary>


12

protected

override

string
GetIssuerName()

13
{

14

//
设置令牌颁发者名称


15

return
STSConfiguration.SSO_STS_ISSUER_ADDRESS;

16
}

17


18

///

<summary>
分析令牌请求
</summary>


19

protected

override
Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)

20
{

21

//
分析RST,逻辑直接在这里判断


22
PolicyOptions options
=
PolicyOptions.Create(request);

23


24

//
地址是否需要验证


25

if
(
!
options.IsKnownRealm)

26
{

27

throw

new
InvalidRequestException(
"
请求地址未通过验证
"
);

28
}

29

//
返回地址能不能跨域


30

if
(
!
options.ReplyToAddressIsWithinRealm)

31
{

32

throw

new
InvalidRequestException(
"
不合法的返回地址
"
);

33
}

34

//
令牌需不需要加密


35

if
(
!
options.UsesEncryption)

36
{

37

throw

new
InvalidRequestException(
"
没找到加密证书
"
);

38
}

39

//
是否需要SSL传输 (passive模式有效)

40

//
if (!options.UsesSsl)

41

//
{

42

//
if (!options.IsActive)

43

//
{

44

//
throw new InvalidRequestException("需要SSL");

45

//
}

46

//
}

47

//
构造 scope


48

return

new
PolicyScope(options, SecurityTokenServiceConfiguration.SigningCredentials);

49
}

50


51

///

<summary>


52

///
生成安全令牌内容,返回声明标示的集合。

53

///

</summary>


54

protected

override
IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)

55
{

56
ClaimsIdentity outputIdentity
=

new
ClaimsIdentity();

57


58

//
这里开始根据主体的标识信息获取用户的详细信息,并按业务逻辑写入要颁发的安全令牌,主体的标识信息是在身份验证的时候写入的,下面会说明

59

//
用户登录名


60
outputIdentity.Claims.Add(
new
Claim(System.IdentityModel.Claims.ClaimTypes.Name, principal.Identity.Name, ClaimValueTypes.String));

61

//
也可以写一些自定义的声明,比如

62

//
用户是否是管理员


63
outputIdentity.Claims.Add(
new
Claim(
"
http://sts-server/claims/isadmin
"
,
"
true
"
, ClaimValueTypes.Boolean));

64


65

return
outputIdentity;

66
}

67


68

///

<summary>


69

///
可选,生成显示令牌,只有在RST的RequestDisplayToken标记为true时才生成

70

///

</summary>


71

protected

override
DisplayToken GetDisplayToken(
string
requestedDisplayTokenLanguage, IClaimsIdentity subject)

72
{

73
var displayClaims
=

new
List
<
DisplayClaim
>
();

74

if
(subject.Claims
!=

null
)

75
{

76

foreach
(var claim
in
subject.Claims)

77
{

78

switch
(claim.ClaimType)

79
{

80

case
WSIdentityConstants.ClaimTypes.Name:

81
displayClaims.Add(GetStandardClaim(claim));

82

break
;

83

default
:

84

break
;

85
}

86
}

87
}

88

return

new
DisplayToken(requestedDisplayTokenLanguage, displayClaims);

89
}

90


91

///

<summary>
标准声明类型的DisplayClaim
</summary>


92

private
DisplayClaim GetStandardClaim(Claim claim)

93
{

94
var displayClaim
=
DisplayClaim.CreateDisplayClaimFromClaimType(claim.ClaimType);

95
displayClaim.DisplayValue
=
claim.Value;

96

return
displayClaim;

97
}

98
}

99
}

100


  1.      其中PolicyScope类也是继承了Geneva框架的基类Scope,用于对RST进行分析,包括:

         请求地址是否为空;

         验证请求地址并获取RP证书用来加密安全令牌;

         请求是否主动模式;

         如果是被动模式返回地址是否跨域;

         是否启用SSL等。

     

         代码略过。

     

         这样STS的主体部分就完成了,但是还缺少一个类,用于验证客户端身份凭据。

         在这里客户端身份验证的方式采用UserName,而这个处理类也可以继承Geneva框架既有的基类:

1. 
1using Microsoft.IdentityModel.Protocols.WSTrust;
2namespace STS.Core
3{
4publicclass STSUserNameSecurityTokenHandler : UserNameSecurityTokenHandler
5{
6///<summary>可以进行身份验证</summary>
7publicoverridebool CanValidateToken
8{
9get
10{
11returntrue;
12}
13}
14
15///<summary>身份验证</summary>
16publicoverride ClaimsIdentityCollection ValidateToken(SecurityToken token)
17{
18if (token ==null)
19{
20ArgumentException e=new ArgumentException("无效的空令牌");
21throw e;
22}
23UserNameSecurityToken usernameToken= token as UserNameSecurityToken;
24if(usernameToken==null)
25{
26ArgumentException e=new ArgumentException("无效的UserName令牌");
27throw e;
28}
29
30// 声明集合,主要记录身份验证的信息
31ClaimsIdentityCollection cc=new ClaimsIdentityCollection();
32IClaimsIdentity identity=new ClaimsIdentity();
33
34//验证客户端身份代码……….
35
36//将验证结果写入标识的声明集合内,供颁发令牌时使用
37//用户名
38identity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.Name, usernameToken.UserName, "DoxtUserNameSecurityTokenHandler"));
39//颁发时间
40identity.Claims.Add(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant", XmlConvert.ToString(DateTime.Now, "yyyy-MM-ddTHH:mm:ssZ"),"http://www.w3.org/2001/XMLSchema#dateTime"));
41cc.Add(identity);
42
43return cc;
44}
45
46///<summary>
47/// 拷贝
48///</summary>
49///<returns></returns>
50publicoverride SecurityTokenHandler Clone()
51{
52returnnew STSUserNameSecurityTokenHandler();
53}
54}
55}
56
57
58
  1.  现在已经完成了Server端的代码,下面是WCF的配置文件,其中服务器证书和自定义的客户端身份验证类并没有在WCF的<behaviors>节点声明,而是放在Geneva框架的节点<microsoft.identityModel>内,需要注意:
1<configSections>
2<sectionname="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
3</configSections>
4<microsoft.identityModel>
5<service>
6<securityTokenHandlers>
7<!--自定义Username令牌处理类-->
8<removetype="Microsoft.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
9<addtype="STS.Core.STSUserNameSecurityTokenHandler, STS.Core"/>
10</securityTokenHandlers>
11<serviceCertificate>
12<!--指定服务端证书-->
13<certificateReferencefindValue="CN=sts-server" storeLocation="LocalMachine" storeName="My"/>
14</serviceCertificate>
15</service>
16</microsoft.identityModel>
17<system.serviceModel>
18<services>
19<servicename="Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract">
20<endpointaddress="wsHttp"
21binding="ws2007HttpBinding" bindingConfiguration="wsHttpUserName"
22contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract">
23</endpoint>
24<endpointaddress="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
25</service>
26</services>
27<bindings>
28<ws2007HttpBinding>
29<bindingname="wsHttpUserName">
30<securitymode="Message">
31<!--指定使用Username进行客户端身份验证,并且需要建立安全上下文-->
32<messageclientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="true"/>
33</security>
34</binding>
35</ws2007HttpBinding>
36</bindings>
37<behaviors>
38<serviceBehaviors>
39<behaviorname="stsBehavior">
40<serviceMetadatahttpGetEnabled="false"/>
41<serviceDebugincludeExceptionDetailInFaults="false"/>
42</behavior>
43</serviceBehaviors>
44</behaviors>
45</system.serviceModel>
46
47