经过前两篇文章,估计大家对WIF已经有比较充分的认识了,估计大家在经过了枯燥的理论以后,肯定是摩拳擦掌赶紧付诸于行动了。没办法,咱们程序员就是这个毛病。那好吧,我也不那么多废话了,直接进入正题吧。

我们接下来的demo将包括以下的工程:

  1. SiteA —— 基于.net framework 4.5的MVC 4程序,使用WIF 4.5的SDK,第一个RP
  2. SiteB —— 基于.net framework 4.5的MVC 4程序,使用WIF 3.5的SDK,第二个RP
  3. SiteC —— 基于.net framework 4.0的MVC 4程序,使用WIF 3.5的SDK,第三个RP
  4. SiteD —— 基于.net framework 4.0 的WebApplication程序,使用WIF 3.5的SDK,第四个RP
  5. STS —— 基于.net framework 4.5 的MVC 4程序,作为IP

一、创建第一个RP

以管理员身份打开vs2012,在起始页上点击“新建项目”,在左边的“模板”树下,展开“其它项目类型”,然后选择“Visual Studio解决方案”,“名称”输入框里输入WIFSSO,然后选择解决方案的路径后点击”确定“,如图:

在”解决方案资源管理器“中,在新建好的解决方案上点右键,选择”添加“->”新建项目“。在弹出的对话框中选择”ASP.NET MVC 4 Web应用程序“,记得.Net Framework版本选4.5,名称起名为”SiteA“,然后点确定,如图:

在弹出的“新ASP.NET MVC 4项目”对话框中直接点“确定”,第一个RP项目新建完成后,添加以下两个引用:System.IdentityModel和System.IdentityModel.Services。这次的教程不使用Identity and Access Tool,而是直接修改web.config文件,这样能使大家对WIF的配置有更深入的了解。

打开web.config文件,将configSections节里的entityFramework配置节点删掉,因为我们不需要用到Entity Framework。最好把web.config中关于Entity Framework相关的配置全都删掉,因为我们都用不上。然后加上以下这两个节点:

  1. <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
  2. <section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
 <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

将authentication节的mode属性设为None,并把里面的form节点删掉,因为我们采用的是WIF的身份验证方式,而不是传统的Forms身份验证。然后增加authorization节点,不允许匿名用户访问站点:

  1. <authorization>
  2. <deny users="?"/>
  3. </authorization>
    <authorization>
<deny users="?"/>
</authorization>

在system.webServer节点下增加2个HttpModule的配置节点:

  1. <modules>
  2. <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
  3. <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
  4. </modules>
    <modules>
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>

最后,增加WIF的配置节点:

  1. <system.identityModel>
  2. <identityConfiguration>
  3. <audienceUris mode="Always">
  4. <add value="http://www.sitea.com" />
  5. </audienceUris>
  6. <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  7. <trustedIssuers>
  8. <add name="http://www.sts.com" thumbprint="FD1425A2F30937786F46E52E43B01AFD54E5D64D"/>
  9. </trustedIssuers>
  10. </issuerNameRegistry>
  11. </identityConfiguration>
  12. </system.identityModel>
  13. <system.identityModel.services>
  14. <federationConfiguration>
  15. <cookieHandler requireSsl="false" />
  16. <wsFederation passiveRedirectEnabled="true" issuer="http://www.sts.com" realm="http://www.sitea.com" reply="http://www.sitea.com" requireHttps="false"/>
  17. </federationConfiguration>
  18. </system.identityModel.services>
  <system.identityModel>
<identityConfiguration>
<audienceUris mode="Always">
<add value="http://www.sitea.com" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedIssuers>
<add name="http://www.sts.com" thumbprint="FD1425A2F30937786F46E52E43B01AFD54E5D64D"/>
</trustedIssuers>
</issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" />
<wsFederation passiveRedirectEnabled="true" issuer="http://www.sts.com" realm="http://www.sitea.com" reply="http://www.sitea.com" requireHttps="false"/>
</federationConfiguration>
</system.identityModel.services>

我来详细解释一下这些节点的意义。audienceUris指定了一组可以被RP接受的身份标识URI,只有这些配置中的URI范围内的令牌才可以被接受。这里,我把siteA配置在这里。trustedIssuers就是受信任的发行者,由于我们这个demo没有用到SSL,所以这里我指定的thumbprint是IIS Express的指纹,这个指纹在哪里可以获得呢?打开IIS管理器,在左侧树点击根节点,然后在“功能视图”里双击“服务器证书",如下图:

在打开的证书列表里,找到IIS Express Development Certificate,双击,在弹出的”证书“对话框中点击“详细信息”页签,找到“指纹”然后点击,把框里的指纹拷下来,全都改成大写后粘贴到thumbnail的值里去:

接下来配置federationConfiguration节点,它表示配置WSFederationAuthenticationModule (WSFAM)  和SessionAuthenticationModule (SAM)  时使用联合身份验证通过的 WS 联合身份验证协议。这里我们使用WS 联合身份验证的身份验证模块 (WSFAM),关于该节点的详细配置信息,请参考:http://msdn.microsoft.com/zh-cn/library/office/apps/hh568665.aspx

好,这样一来,SiteA的配置就已经完成了,然后我们来加点代码。

打开/Views/Home/Index.cshtml,将原有的代码删掉,改为如下代码:

  1. @using System.Security.Claims
  2. @{
  3. ViewBag.Title = "SiteA主页";
  4. ClaimsIdentity ci = User.Identity as ClaimsIdentity;
  5. if(ci!=null)
  6. {
  7. <h2>@ci.FindFirst(ClaimTypes.Name).Value</h2>
  8. <h2>@ci.FindFirst(ClaimTypes.Email).Value</h2>
  9. }
  10. }
  11. <a href="http://www.sts.com/Account/LogOff">退出</a>
@using System.Security.Claims
@{
ViewBag.Title = "SiteA主页";
ClaimsIdentity ci = User.Identity as ClaimsIdentity;
if(ci!=null)
{
<h2>@ci.FindFirst(ClaimTypes.Name).Value</h2>
<h2>@ci.FindFirst(ClaimTypes.Email).Value</h2>
}
} <a href="http://www.sts.com/Account/LogOff">退出</a>

代码很简单,只要当前用户处于已登录状态,就把用户的名称和Email显示在页面上。

至此,SiteA就已经完成了。你是不是迫不及待的想要运行了呢?别急,虽然有SiteA了,但还没有STS呢,现在启动SiteA,由于没登录,所以它会跳转到STS,但STS还不存在,所以会出错的。

二、创建STS

接下来我们来创建STS,在解决方案上新建项目,新建一个名为STS的MVC 4应用程序,.Net Framework选择4.5,项目模板选择“Internet应用程序",确定。

添加System.IdentityModel和System.IdentityModel.Services这两个引用,打开web.config,为forms节点添加两个属性:

  1. <forms loginUrl="~/Account/Login" timeout="2880" slidingExpiration="true" name=".STSASPAUTH" />
<forms loginUrl="~/Account/Login" timeout="2880" slidingExpiration="true" name=".STSASPAUTH" />

在AppSettings里增加如下三个节点:

  1. <add key="IssuerName" value="PassiveSigninSTS" />
  2. <add key="SigningCertificateName" value="CN=localhost" />
  3. <add key="EncryptingCertificateName" value="" />
    <add key="IssuerName" value="PassiveSigninSTS" />
<add key="SigningCertificateName" value="CN=localhost" />
<add key="EncryptingCertificateName" value="" />

同样禁止匿名用户访问:

  1. <authorization>
  2. <deny users="?"/>
  3. </authorization>
    <authorization>
<deny users="?"/>
</authorization>

在应用程序下新建一个名为Services的文件夹,在里面新建一个类文件,名为:CertificateUtil,用于获取证书,具体代码如下:

  1. public class CertificateUtil
  2. {
  3. public static X509Certificate2 GetCertificate(StoreName name, StoreLocation location, string subjectName)
  4. {
  5. X509Store store = new X509Store(name, location);
  6. X509Certificate2Collection certificates = null;
  7. store.Open(OpenFlags.ReadOnly);
  8. try
  9. {
  10. X509Certificate2 result = null;
  11. certificates = store.Certificates;
  12. for (int i = 0; i < certificates.Count; i++)
  13. {
  14. X509Certificate2 cert = certificates[i];
  15. if (cert.SubjectName.Name.ToLower() == subjectName.ToLower())
  16. {
  17. if (result != null)
  18. throw new ApplicationException(string.Format("subject Name {0}存在多个证书", subjectName));
  19. result = new X509Certificate2(cert);
  20. }
  21. }
  22. if (result == null)
  23. {
  24. throw new ApplicationException(string.Format("没有找到用于 subject Name {0} 的证书", subjectName));
  25. }
  26. return result;
  27. }
  28. finally
  29. {
  30. if (certificates != null)
  31. {
  32. for (int i = 0; i < certificates.Count; i++)
  33. {
  34. certificates[i].Reset();
  35. }
  36. }
  37. store.Close();
  38. }
  39. }
  40. }
    public class CertificateUtil
{
public static X509Certificate2 GetCertificate(StoreName name, StoreLocation location, string subjectName)
{
X509Store store = new X509Store(name, location);
X509Certificate2Collection certificates = null;
store.Open(OpenFlags.ReadOnly); try
{
X509Certificate2 result = null;
certificates = store.Certificates; for (int i = 0; i < certificates.Count; i++)
{
X509Certificate2 cert = certificates[i];
if (cert.SubjectName.Name.ToLower() == subjectName.ToLower())
{
if (result != null)
throw new ApplicationException(string.Format("subject Name {0}存在多个证书", subjectName));
result = new X509Certificate2(cert);
}
} if (result == null)
{
throw new ApplicationException(string.Format("没有找到用于 subject Name {0} 的证书", subjectName));
} return result;
}
finally
{
if (certificates != null)
{
for (int i = 0; i < certificates.Count; i++)
{
certificates[i].Reset();
}
}
store.Close();
}
} }

创建新类,名为Common,存放几个常量:

  1. public class Common
  2. {
  3. public const string IssuerName = "IssuerName";
  4. public const string SigningCertificateName = "SigningCertificateName";
  5. public const string EncryptingCertificateName = "EncryptingCertificateName";
  6. }
    public class Common
{
public const string IssuerName = "IssuerName";
public const string SigningCertificateName = "SigningCertificateName";
public const string EncryptingCertificateName = "EncryptingCertificateName"; }

创建新类,名为SingleSignOnManager,用于注册RP以及获取RP列表:

  1. public class SingleSignOnManager
  2. {
  3. const string SITECOOKIENAME = "StsSiteCookie";
  4. const string SITENAME = "StsSite";
  5. /// <summary>
  6. /// Returns a list of sites the user is logged in via the STS
  7. /// </summary>
  8. /// <returns></returns>
  9. public static string[] SignOut()
  10. {
  11. if (HttpContext.Current != null &&
  12. HttpContext.Current.Request != null &&
  13. HttpContext.Current.Request.Cookies != null
  14. )
  15. {
  16. HttpCookie siteCookie =
  17. HttpContext.Current.Request.Cookies[SITECOOKIENAME];
  18. if (siteCookie != null)
  19. return siteCookie.Values.GetValues(SITENAME);
  20. }
  21. return new string[0];
  22. }
  23. public static void RegisterRP(string SiteUrl)
  24. {
  25. if (HttpContext.Current != null &&
  26. HttpContext.Current.Request != null &&
  27. HttpContext.Current.Request.Cookies != null
  28. )
  29. {
  30. // get an existing cookie or create a new one
  31. HttpCookie siteCookie =
  32. HttpContext.Current.Request.Cookies[SITECOOKIENAME];
  33. if (siteCookie == null)
  34. siteCookie = new HttpCookie(SITECOOKIENAME);
  35. siteCookie.Values.Add(SITENAME, SiteUrl);
  36. HttpContext.Current.Response.AppendCookie(siteCookie);
  37. }
  38. }
  39. }
    public class SingleSignOnManager
{
const string SITECOOKIENAME = "StsSiteCookie";
const string SITENAME = "StsSite"; /// <summary>
/// Returns a list of sites the user is logged in via the STS
/// </summary>
/// <returns></returns>
public static string[] SignOut()
{
if (HttpContext.Current != null &&
HttpContext.Current.Request != null &&
HttpContext.Current.Request.Cookies != null
)
{
HttpCookie siteCookie =
HttpContext.Current.Request.Cookies[SITECOOKIENAME]; if (siteCookie != null)
return siteCookie.Values.GetValues(SITENAME);
} return new string[0];
} public static void RegisterRP(string SiteUrl)
{
if (HttpContext.Current != null &&
HttpContext.Current.Request != null &&
HttpContext.Current.Request.Cookies != null
)
{
// get an existing cookie or create a new one
HttpCookie siteCookie =
HttpContext.Current.Request.Cookies[SITECOOKIENAME];
if (siteCookie == null)
siteCookie = new HttpCookie(SITECOOKIENAME); siteCookie.Values.Add(SITENAME, SiteUrl); HttpContext.Current.Response.AppendCookie(siteCookie);
}
} }

创建新类,CustomSecurityTokenService,自定义令牌服务,继承SecurityTokenService,用于返回需要的声明令牌:

  1. public class CustomSecurityTokenService : SecurityTokenService
  2. {
  3. private readonly SigningCredentials signingCreds;
  4. private readonly EncryptingCredentials encryptingCreds;
  5. public CustomSecurityTokenService(SecurityTokenServiceConfiguration config)
  6. : base(config)
  7. {
  8. this.signingCreds = new X509SigningCredentials(
  9. CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, WebConfigurationManager.AppSettings[Common.SigningCertificateName]));
  10. if (!string.IsNullOrWhiteSpace(WebConfigurationManager.AppSettings[Common.EncryptingCertificateName]))
  11. {
  12. this.encryptingCreds = new X509EncryptingCredentials(
  13. CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, WebConfigurationManager.AppSettings[Common.EncryptingCertificateName]));
  14. }
  15. }
  16. /// <summary>
  17. /// 此方法返回要发布的令牌内容。内容由一组ClaimsIdentity实例来表示,每一个实例对应了一个要发布的令牌。当前Windows Identity Foundation只支持单个令牌发布,因此返回的集合必须总是只包含单个实例。
  18. /// </summary>
  19. /// <param name="principal">调用方的principal</param>
  20. /// <param name="request">进入的 RST,我们这里不用它</param>
  21. /// <param name="scope">由之前通过GetScope方法返回的范围</param>
  22. /// <returns></returns>
  23. protected override ClaimsIdentity GetOutputClaimsIdentity(ClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
  24. {
  25. //返回一个默认声明集,里面了包含自己想要的声明
  26. //这里你可以通过ClaimsPrincipal来验证用户,并通过它来返回正确的声明。
  27. string identityName = principal.Identity.Name;
  28. string[] temp = identityName.Split('|');
  29. ClaimsIdentity outgoingIdentity = new ClaimsIdentity();
  30. outgoingIdentity.AddClaim(new Claim(ClaimTypes.Email, temp[0]));
  31. outgoingIdentity.AddClaim(new Claim(ClaimTypes.DateOfBirth, temp[1]));
  32. outgoingIdentity.AddClaim(new Claim(ClaimTypes.Name, temp[2]));
  33. SingleSignOnManager.RegisterRP(scope.AppliesToAddress);
  34. return outgoingIdentity;
  35. }
  36. /// <summary>
  37. /// 此方法返回用于令牌发布请求的配置。配置由Scope类表示。在这里,我们只发布令牌到一个由encryptingCreds字段表示的RP标识        /// </summary>
  38. /// <param name="principal"></param>
  39. /// <param name="request"></param>
  40. /// <returns></returns>
  41. protected override Scope GetScope(ClaimsPrincipal principal, RequestSecurityToken request)
  42. {
  43. // 使用request的AppliesTo属性和RP标识来创建Scope
  44. Scope scope = new Scope(request.AppliesTo.Uri.AbsoluteUri, this.signingCreds);
  45. if (Uri.IsWellFormedUriString(request.ReplyTo, UriKind.Absolute))
  46. {
  47. if (request.AppliesTo.Uri.Host != new Uri(request.ReplyTo).Host)
  48. scope.ReplyToAddress = request.AppliesTo.Uri.AbsoluteUri;
  49. else
  50. scope.ReplyToAddress = request.ReplyTo;
  51. }
  52. else
  53. {
  54. Uri resultUri = null;
  55. if (Uri.TryCreate(request.AppliesTo.Uri, request.ReplyTo, out resultUri))
  56. scope.ReplyToAddress = resultUri.AbsoluteUri;
  57. else
  58. scope.ReplyToAddress = request.AppliesTo.Uri.ToString();
  59. }
  60. if (this.encryptingCreds != null)
  61. {
  62. // 如果STS对应多个RP,要选择证书指定到请求令牌的RP,然后再用 encryptingCreds
  63. scope.EncryptingCredentials = this.encryptingCreds;
  64. }
  65. else
  66. scope.TokenEncryptionRequired = false;
  67. return scope;
  68. }
  69. }
    public class CustomSecurityTokenService : SecurityTokenService
{
private readonly SigningCredentials signingCreds;
private readonly EncryptingCredentials encryptingCreds; public CustomSecurityTokenService(SecurityTokenServiceConfiguration config)
: base(config)
{
this.signingCreds = new X509SigningCredentials(
CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, WebConfigurationManager.AppSettings[Common.SigningCertificateName])); if (!string.IsNullOrWhiteSpace(WebConfigurationManager.AppSettings[Common.EncryptingCertificateName]))
{
this.encryptingCreds = new X509EncryptingCredentials(
CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, WebConfigurationManager.AppSettings[Common.EncryptingCertificateName]));
}
} /// <summary>
/// 此方法返回要发布的令牌内容。内容由一组ClaimsIdentity实例来表示,每一个实例对应了一个要发布的令牌。当前Windows Identity Foundation只支持单个令牌发布,因此返回的集合必须总是只包含单个实例。
/// </summary>
/// <param name="principal">调用方的principal</param>
/// <param name="request">进入的 RST,我们这里不用它</param>
/// <param name="scope">由之前通过GetScope方法返回的范围</param>
/// <returns></returns>
protected override ClaimsIdentity GetOutputClaimsIdentity(ClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
{
//返回一个默认声明集,里面了包含自己想要的声明
//这里你可以通过ClaimsPrincipal来验证用户,并通过它来返回正确的声明。
string identityName = principal.Identity.Name;
string[] temp = identityName.Split('|');
ClaimsIdentity outgoingIdentity = new ClaimsIdentity();
outgoingIdentity.AddClaim(new Claim(ClaimTypes.Email, temp[0]));
outgoingIdentity.AddClaim(new Claim(ClaimTypes.DateOfBirth, temp[1]));
outgoingIdentity.AddClaim(new Claim(ClaimTypes.Name, temp[2]));
SingleSignOnManager.RegisterRP(scope.AppliesToAddress);
return outgoingIdentity;
} /// <summary>
/// 此方法返回用于令牌发布请求的配置。配置由Scope类表示。在这里,我们只发布令牌到一个由encryptingCreds字段表示的RP标识 /// </summary>
/// <param name="principal"></param>
/// <param name="request"></param>
/// <returns></returns>
protected override Scope GetScope(ClaimsPrincipal principal, RequestSecurityToken request)
{
// 使用request的AppliesTo属性和RP标识来创建Scope
Scope scope = new Scope(request.AppliesTo.Uri.AbsoluteUri, this.signingCreds); if (Uri.IsWellFormedUriString(request.ReplyTo, UriKind.Absolute))
{
if (request.AppliesTo.Uri.Host != new Uri(request.ReplyTo).Host)
scope.ReplyToAddress = request.AppliesTo.Uri.AbsoluteUri;
else
scope.ReplyToAddress = request.ReplyTo;
}
else
{
Uri resultUri = null;
if (Uri.TryCreate(request.AppliesTo.Uri, request.ReplyTo, out resultUri))
scope.ReplyToAddress = resultUri.AbsoluteUri;
else
scope.ReplyToAddress = request.AppliesTo.Uri.ToString();
}
if (this.encryptingCreds != null)
{
// 如果STS对应多个RP,要选择证书指定到请求令牌的RP,然后再用 encryptingCreds
scope.EncryptingCredentials = this.encryptingCreds;
}
else
scope.TokenEncryptionRequired = false;
return scope;
}
}

最后添加新类CustomSecurityTokenServiceConfiguration,继承SecurityTokenServiceConfiguration:

  1. public class CustomSecurityTokenServiceConfiguration : SecurityTokenServiceConfiguration
  2. {
  3. private static readonly object syncRoot = new object();
  4. private const string CustomSecurityTokenServiceConfigurationKey = "CustomSecurityTokenServiceConfigurationKey";
  5. public CustomSecurityTokenServiceConfiguration()
  6. : base(WebConfigurationManager.AppSettings[Common.IssuerName])
  7. {
  8. this.SecurityTokenService = typeof(CustomSecurityTokenService);
  9. }
  10. public static CustomSecurityTokenServiceConfiguration Current
  11. {
  12. get
  13. {
  14. HttpApplicationState app = HttpContext.Current.Application;
  15. CustomSecurityTokenServiceConfiguration config = app.Get(CustomSecurityTokenServiceConfigurationKey) as CustomSecurityTokenServiceConfiguration;
  16. if (config != null)
  17. return config;
  18. lock (syncRoot)
  19. {
  20. config = app.Get(CustomSecurityTokenServiceConfigurationKey) as CustomSecurityTokenServiceConfiguration;
  21. if (config == null)
  22. {
  23. config = new CustomSecurityTokenServiceConfiguration();
  24. app.Add(CustomSecurityTokenServiceConfigurationKey, config);
  25. }
  26. return config;
  27. }
  28. }
  29. }
  30. }
    public class CustomSecurityTokenServiceConfiguration : SecurityTokenServiceConfiguration
{
private static readonly object syncRoot = new object();
private const string CustomSecurityTokenServiceConfigurationKey = "CustomSecurityTokenServiceConfigurationKey"; public CustomSecurityTokenServiceConfiguration()
: base(WebConfigurationManager.AppSettings[Common.IssuerName])
{
this.SecurityTokenService = typeof(CustomSecurityTokenService);
} public static CustomSecurityTokenServiceConfiguration Current
{
get
{
HttpApplicationState app = HttpContext.Current.Application;
CustomSecurityTokenServiceConfiguration config = app.Get(CustomSecurityTokenServiceConfigurationKey) as CustomSecurityTokenServiceConfiguration;
if (config != null)
return config;
lock (syncRoot)
{
config = app.Get(CustomSecurityTokenServiceConfigurationKey) as CustomSecurityTokenServiceConfiguration;
if (config == null)
{
config = new CustomSecurityTokenServiceConfiguration();
app.Add(CustomSecurityTokenServiceConfigurationKey, config);
} return config;
}
}
}
}

打开/Controllers/HomeController.cs,将Index()方法修改如下:

  1. public ActionResult Index()
  2. {
  3. FederatedPassiveSecurityTokenServiceOperations.ProcessRequest(
  4. System.Web.HttpContext.Current.Request,
  5. User as ClaimsPrincipal,
  6. CustomSecurityTokenServiceConfiguration.Current.CreateSecurityTokenService(),
  7. System.Web.HttpContext.Current.Response);
  8. return View();
  9. }
        public ActionResult Index()
{
FederatedPassiveSecurityTokenServiceOperations.ProcessRequest(
System.Web.HttpContext.Current.Request,
User as ClaimsPrincipal,
CustomSecurityTokenServiceConfiguration.Current.CreateSecurityTokenService(),
System.Web.HttpContext.Current.Response);
return View();
}

打开/Controllers/AccountController.cs,将Login(LoginModel model, string returnUrl)方法修改如下:

  1. [HttpPost]
  2. [AllowAnonymous]
  3. [ValidateAntiForgeryToken]
  4. public ActionResult Login(LoginModel model, string returnUrl)
  5. {
  6. var query = HttpUtility.ParseQueryString(Request.UrlReferrer.Query);
  7. if (model.UserName == "ojlovecd@csdn.net" && model.Password == "123456")
  8. {
  9. FormsAuthentication.SetAuthCookie("ojlovecd@csdn.net|1983-10-22|oujian", false);
  10. if (!string.IsNullOrEmpty(returnUrl))
  11. return Redirect(returnUrl);
  12. return RedirectToAction("Index", "Home");
  13. }
  14. return View(model);
  15. }
        [HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
var query = HttpUtility.ParseQueryString(Request.UrlReferrer.Query);
            if (model.UserName == "ojlovecd@csdn.net" && model.Password == "123456")
            {
                FormsAuthentication.SetAuthCookie("ojlovecd@csdn.net|1983-10-22|oujian", false);
                if (!string.IsNullOrEmpty(returnUrl))
                    return Redirect(returnUrl);
                return RedirectToAction("Index", "Home");
            }
            return View(model);
}

LogOff方法修改如下:

  1. public ActionResult LogOff()
  2. {
  3. FormsAuthentication.SignOut();
  4. ViewData["AddressesExpected"] = SingleSignOnManager.SignOut().Distinct().ToArray();
  5. return View("Login");
  6. }
        public ActionResult LogOff()
{
FormsAuthentication.SignOut();
            ViewData["AddressesExpected"] = SingleSignOnManager.SignOut().Distinct().ToArray();
            return View("Login");
}

打开/Views/Account/Login.cshtml,添加以下代码:

  1. @{
  2. ViewBag.Title = "登录";
  3. var addressesExpected = ViewData["AddressesExpected"] as string[];
  4. if (addressesExpected != null)
  5. {
  6. foreach (var address in addressesExpected)
  7. {
  8. <img src="@(address)?wa=wsignoutcleanup1.0" style="display:none;" />
  9. }
  10. }
  11. }
@{
ViewBag.Title = "登录";
var addressesExpected = ViewData["AddressesExpected"] as string[];
if (addressesExpected != null)
{
foreach (var address in addressesExpected)
{
<img src="@(address)?wa=wsignoutcleanup1.0" style="display:none;" />
}
} }

OK,至此STS也已经完成了。把SiteA和STS都部署到IIS上,然后打开C:\Windows\System32\Drivers\etc\hosts文件,添加几个站点:

  1. 127.0.0.1   www.sitea.com
  2. 127.0.0.1   www.siteb.com
  3. 127.0.0.1   www.sitec.com
  4. 127.0.0.1   www.sited.com
  5. 127.0.0.1   www.sts.com
127.0.0.1	www.sitea.com
127.0.0.1 www.siteb.com
127.0.0.1 www.sitec.com
127.0.0.1 www.sited.com
127.0.0.1 www.sts.com

好了,在浏览器输入www.sitea.com,看看如何,它马上跳转到了www.sts.com的登录页面,输入ojlovecd@csdn.net,密码123456,确定,登录成功,跳回到了www.sitea.com,并显示出了用户名和Email:

点击退出,将注销当前用户,并跳转到登录页。

三、创建其它RP

OK,站点A搞定了,那其它站点如何呢?现在只是最简单的登录退出功能而已,说好的单点登录呢? 别急,接下来就一一实现。 新建基于.NET Framework4.5的MVC4程序,添加Microsoft.IdentityModel引用。修改web.config,configSections里添加如下节点:

  1. <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

Compilation里增加Microsoft.IdentityModel的程序集:

  1. <compilation debug="true" targetFramework="4.5" >
  2. <assemblies>
  3. <add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  4. </assemblies>
  5. </compilation>
    <compilation debug="true" targetFramework="4.5" >
<assemblies>
<add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</assemblies>
</compilation>

身份验证改为None,添加authorization节点,禁止匿名用户访问:

  1. <authentication mode="None">
  2. </authentication>
  3. <authorization>
  4. <deny users="?" />
  5. </authorization>
    <authentication mode="None">
</authentication>
<authorization>
<deny users="?" />
</authorization>

添加三个httpModules:

  1. <httpModules>
  2. <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  3. <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  4. <add name="ClaimsAuthorizationModule" type="Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  5. </httpModules>
  6. system.webServer里添加以下三个modules:
  7. <modules >
  8. <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
  9. <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
  10. <add name="ClaimsAuthorizationModule" type="Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
  11. </modules>
<httpModules>
<add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="ClaimsAuthorizationModule" type="Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</httpModules>
system.webServer里添加以下三个modules:
<modules >
<add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
<add name="ClaimsAuthorizationModule" type="Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
</modules>

最后增加microsoft.identityModel节点:

  1. <microsoft.identityModel>
  2. <service>
  3. <audienceUris mode="Always">
  4. <add value="http://www.siteb.com" />
  5. </audienceUris>
  6. <federatedAuthentication>
  7. <wsFederation passiveRedirectEnabled="true" issuer="http://www.sts.com" realm="http://www.siteb.com" reply="http://www.siteb.com"  requireHttps="false" />
  8. <cookieHandler requireSsl="false" />
  9. </federatedAuthentication>
  10. <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
  11. <trustedIssuers>
  12. <add thumbprint="FD1425A2F30937786F46E52E43B01AFD54E5D64D" name="http://www.sts.com" />
  13. </trustedIssuers>
  14. </issuerNameRegistry>
  15. </service>
  16. </microsoft.identityModel>
 <microsoft.identityModel>
<service>
<audienceUris mode="Always">
<add value="http://www.siteb.com" />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true" issuer="http://www.sts.com" realm="http://www.siteb.com" reply="http://www.siteb.com" requireHttps="false" />
<cookieHandler requireSsl="false" />
</federatedAuthentication>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<trustedIssuers>
<add thumbprint="FD1425A2F30937786F46E52E43B01AFD54E5D64D" name="http://www.sts.com" />
</trustedIssuers>
</issuerNameRegistry>
</service>
</microsoft.identityModel>

以上配置跟SIteA差不多,只是WIF3.5和4.5的区别而已,在这里就不赘述了,要获取详细信息,请参考微软官方网站。 打开/Views/Home/Index.cshtml,将代码修改如下,在SiteB里我们显示Email和生日:

  1. @using Microsoft.IdentityModel.Claims
  2. @{
  3. ViewBag.Title = "SiteB主页";
  4. ClaimsIdentity ci = User.Identity as ClaimsIdentity;
  5. if(ci!=null)
  6. {
  7. <h2>@ci.Claims.SingleOrDefault(c=>c.ClaimType == ClaimTypes.Email).Value</h2>
  8. <h2>@ci.Claims.SingleOrDefault(c=>c.ClaimType == ClaimTypes.DateOfBirth).Value</h2>
  9. }
  10. }
  11. <a href="http://www.sts.com/Account/LogOff">退出</a>
@using Microsoft.IdentityModel.Claims
@{
ViewBag.Title = "SiteB主页";
ClaimsIdentity ci = User.Identity as ClaimsIdentity;
if(ci!=null)
{
<h2>@ci.Claims.SingleOrDefault(c=>c.ClaimType == ClaimTypes.Email).Value</h2>
<h2>@ci.Claims.SingleOrDefault(c=>c.ClaimType == ClaimTypes.DateOfBirth).Value</h2>
}
} <a href="http://www.sts.com/Account/LogOff">退出</a>

OK,部署到IIS上,然后运行,页面跳转到了sts的登录页面,输入用户名和密码,跳转,哎哟我去,怎么报错了:
原因是从sts返回来的数据里有<>这种标签,于是asp.net认为那是有危险的,于是抛出了异常,这个异常大家估计以前也碰到过,最简单粗暴的方法就是把验证请求的配置改为false,但这里我不建议这么干, 为此,我们专门用一个类来处理这种情况。 在SiteB目录下新建一个文件夹名为Services,然后添加一个类,名为SampleRequestValidator:

  1. /// <summary>
  2. /// This SampleRequestValidator validates the wresult parameter of the
  3. /// WS-Federation passive protocol by checking for a SignInResponse message
  4. /// in the form post. The SignInResponse message contents are verified later by
  5. /// the WSFederationPassiveAuthenticationModule or the WIF signin controls.
  6. /// </summary>
  7. public class SampleRequestValidator : RequestValidator
  8. {
  9. protected override bool IsValidRequestString(HttpContext context, string value, RequestValidationSource requestValidationSource, string collectionKey, out int validationFailureIndex)
  10. {
  11. validationFailureIndex = 0;
  12. if (requestValidationSource == RequestValidationSource.Form && collectionKey.Equals(WSFederationConstants.Parameters.Result, StringComparison.Ordinal))
  13. {
  14. return true;
  15. }
  16. return base.IsValidRequestString(context, value, requestValidationSource, collectionKey, out validationFailureIndex);
  17. }
  18. }
  /// <summary>
/// This SampleRequestValidator validates the wresult parameter of the
/// WS-Federation passive protocol by checking for a SignInResponse message
/// in the form post. The SignInResponse message contents are verified later by
/// the WSFederationPassiveAuthenticationModule or the WIF signin controls.
/// </summary> public class SampleRequestValidator : RequestValidator
{
protected override bool IsValidRequestString(HttpContext context, string value, RequestValidationSource requestValidationSource, string collectionKey, out int validationFailureIndex)
{
validationFailureIndex = 0; if (requestValidationSource == RequestValidationSource.Form && collectionKey.Equals(WSFederationConstants.Parameters.Result, StringComparison.Ordinal))
{
return true;
} return base.IsValidRequestString(context, value, requestValidationSource, collectionKey, out validationFailureIndex);
} }

然后在web.config里加入这个类的配置:

  1. <httpRuntime targetFramework="4.5" requestValidationType="SiteC.Services.SampleRequestValidator" />
    <httpRuntime targetFramework="4.5" requestValidationType="SiteC.Services.SampleRequestValidator" />

重新运行程序,非常完美:
这时候再打开SIteA,发现也已经处于了登录状态,这时候在SiteA点击退出,跳转到了登录页,再看看这时候的SiteB呢,刷新SiteB首页,发现也跳转到了登录页,证明在SiteA的退出操作对SiteB也起了作用,确实是单点登录了! SiteC和SiteD的配置与SiteB类似,这里我就不重复了,留给大家自己练习一下,等所有的项目都配置好以后,在任意站点登录,发现其它站点也是登录状态;在任意站点退出,发现其它站点也已经退出。利用WIF,单点登录变的如此简单~~

使用WIF实现单点登录Part III —— 正式实战 -摘自网络的更多相关文章

  1. 使用WIF实现单点登录Part III —— 正式实战

    我们接下来的demo将包括以下的工程: SiteA —— 基于.net framework 4.5的MVC 4程序,使用WIF 4.5的SDK,第一个RP SiteB —— 基于.net framew ...

  2. ABP集成WIF实现单点登录

    ABP集成WIF实现单点登录 参考 ojlovecd写了三篇关于WIF文章. 使用WIF实现单点登录Part III —— 正式实战 使用WIF的一些开源示例. https://github.com/ ...

  3. 使用WIF实现单点登录Part IV —— 常见问题

    InvalidOperationException: ID1073: 尝试使用 ProtectedData API 解密 Cookie 时出现 CryptographicException (有关详细 ...

  4. 使用WIF实现单点登录Part II —— Windows Identity Foundation基本原理

    在上一篇文章中,我们已经使用WIF构建了一个基于MVC4的简单的身份验证程序,在这篇文章里,我们将探讨一下到底什么是WIF,以及它的工作原理.然后在下一篇文章开始,我们将实际操作,实现单点登录功能. ...

  5. 使用WIF实现单点登录Part II —— Windows Identity Foundation基本原理 -摘自网络

    在上一篇文章中,我们已经使用WIF构建了一个基于MVC4的简单的身份验证程序,在这篇文章里,我们将探讨一下到底什么是WIF,以及它的工作原理.然后在下一篇文章开始,我们将实际操作,实现单点登录功能. ...

  6. 使用WIF实现单点登录Part I——Windows Identity Foundation介绍及环境搭建 -摘自网络

    上个月有一个星期的时间都在研究asp.net mvc统一身份验证及单点登录的实现.经过了一番的探索,最终决定使用微软的Windows Identity Foundation.但是这东西用的人貌似不多, ...

  7. 使用WIF实现单点登录Part I——Windows Identity Foundation介绍及环境搭建

    首先先说一下什么是WIF(Windows Identity Foundation).由于各种历史原因,身份验证和标识的管理一般都比较无规律可循.在软件里加入“身份验证”功能意味着要在你的代码里混进处理 ...

  8. 实战:ADFS3.0单点登录系列-集成MVC

    本文将讲解如何让MVC应用程序与ADFS集成,完成认证的过程. 目录: 实战:ADFS3.0单点登录系列-总览 实战:ADFS3.0单点登录系列-前置准备 实战:ADFS3.0单点登录系列-ADFS3 ...

  9. 实战:ADFS3.0单点登录系列-总览

    本系列将以一个实际项目为背景,介绍如何使用ADFS3.0实现SSO.其中包括SharePoint,MVC,Exchange等应用程序的SSO集成. 整个系列将会由如下几个部分构成: 实战:ADFS3. ...

随机推荐

  1. poj 1348 Computing (四个数的加减乘除四则运算)

    http://poj.org/problem?id=1348 Computing Time Limit: 1000MS   Memory Limit: 10000K Total Submissions ...

  2. 关于http客户端常见错误"警告:Going to buffer response body of large or unknown size. Using getResponseBodyAsStream instead is rec"

    在开发过程中,经常得写http客户端测试接口服务,今天在使用过程中出现了这样的一个警告: 警告: Going to buffer response body of large or unknown s ...

  3. nodejs 第一次使用

    在win7下安装与使用 1 nodejs官网下载,安装  https://nodejs.org/ 2 下载最新的 npm,在E:\nodejs\中解压  http://nodejs.org/dist/ ...

  4. Oracle删除表、字段之前判断表、字段是否存在

    这篇文章主要介绍了Oracle删除表.字段之前判断表.字段是否存在的相关资料,需要的朋友可以参考下 在Oracle中若删除一个不存在的表,如 “DROP TABLE tableName”,则会提示: ...

  5. JS获取IP、MAC和主机名的几种方法

    方法一(只针对IE且客户端的IE允许AcitiveX运行,通过平台:XP,SERVER03,2000): 获取客户端IP. <HTML> <HEAD> <TITLE> ...

  6. WEB黑客工具箱之LiveHttpHeaders介绍

    一.LiveHttpHeaders之安装 自行百度 二.LiveHttpHeaders主窗口 根据我们目的的不同,LiveHttpHeaders有两种启动方法:当我们只想监视通信量的时候,可以从浏览器 ...

  7. codeforces 388C Fox and Card Game

    刚刚看到这个题感觉是博弈题: 不过有感觉不像,应该是个贪心: 于是就想贪心策略: 举了一个例子: 3 3 1 2 3 4 3 4 1 2 5 4 1 2 5 8 如果他们两个每次都拿对自己最有利的那个 ...

  8. linux下安装jira详细步骤

    首先从官网下载jdk的安装包,将jdk的安装包上传到虚拟机或者服务器,在./usr/local/目录下面创建一个java目录:mkdir java 等等,具体祥看本文,希望对你有所帮助 linux下安 ...

  9. 转载爱哥自定义View系列--Canvas详解

    上面所罗列出来的各种drawXXX方法就是Canvas中定义好的能画什么的方法(drawPaint除外),除了各种基本型比如矩形圆形椭圆直曲线外Canvas也能直接让我们绘制各种图片以及颜色等等,但是 ...

  10. 公司估值(贴现现金流量法DCF)

    创业公司总会遇到并购或者入股等情况,CEO需要了解一些公司估值的方法,本文主要介绍贴现现金流量估值方法,供大家参考: 中国资产评估协会要求:在对企业价值进行评估时,应分析收益法.市场法和资产基础法三种 ...