使用WIF实现单点登录Part III —— 正式实战 -摘自网络
经过前两篇文章,估计大家对WIF已经有比较充分的认识了,估计大家在经过了枯燥的理论以后,肯定是摩拳擦掌赶紧付诸于行动了。没办法,咱们程序员就是这个毛病。那好吧,我也不那么多废话了,直接进入正题吧。
我们接下来的demo将包括以下的工程:
- SiteA —— 基于.net framework 4.5的MVC 4程序,使用WIF 4.5的SDK,第一个RP
- SiteB —— 基于.net framework 4.5的MVC 4程序,使用WIF 3.5的SDK,第二个RP
- SiteC —— 基于.net framework 4.0的MVC 4程序,使用WIF 3.5的SDK,第三个RP
- SiteD —— 基于.net framework 4.0 的WebApplication程序,使用WIF 3.5的SDK,第四个RP
- 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相关的配置全都删掉,因为我们都用不上。然后加上以下这两个节点:
- <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" />
<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节点,不允许匿名用户访问站点:
- <authorization>
- <deny users="?"/>
- </authorization>
<authorization>
<deny users="?"/>
</authorization>
在system.webServer节点下增加2个HttpModule的配置节点:
- <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>
<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的配置节点:
- <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>
<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,将原有的代码删掉,改为如下代码:
- @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>
@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节点添加两个属性:
- <forms loginUrl="~/Account/Login" timeout="2880" slidingExpiration="true" name=".STSASPAUTH" />
<forms loginUrl="~/Account/Login" timeout="2880" slidingExpiration="true" name=".STSASPAUTH" />
在AppSettings里增加如下三个节点:
- <add key="IssuerName" value="PassiveSigninSTS" />
- <add key="SigningCertificateName" value="CN=localhost" />
- <add key="EncryptingCertificateName" value="" />
<add key="IssuerName" value="PassiveSigninSTS" />
<add key="SigningCertificateName" value="CN=localhost" />
<add key="EncryptingCertificateName" value="" />
同样禁止匿名用户访问:
- <authorization>
- <deny users="?"/>
- </authorization>
<authorization>
<deny users="?"/>
</authorization>
在应用程序下新建一个名为Services的文件夹,在里面新建一个类文件,名为:CertificateUtil,用于获取证书,具体代码如下:
- 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();
- }
- }
- }
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,存放几个常量:
- public class Common
- {
- public const string IssuerName = "IssuerName";
- public const string SigningCertificateName = "SigningCertificateName";
- public const string EncryptingCertificateName = "EncryptingCertificateName";
- }
public class Common
{
public const string IssuerName = "IssuerName";
public const string SigningCertificateName = "SigningCertificateName";
public const string EncryptingCertificateName = "EncryptingCertificateName"; }
创建新类,名为SingleSignOnManager,用于注册RP以及获取RP列表:
- 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);
- }
- }
- }
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,用于返回需要的声明令牌:
- 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;
- }
- }
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:
- 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;
- }
- }
- }
- }
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()方法修改如下:
- public ActionResult Index()
- {
- FederatedPassiveSecurityTokenServiceOperations.ProcessRequest(
- System.Web.HttpContext.Current.Request,
- User as ClaimsPrincipal,
- CustomSecurityTokenServiceConfiguration.Current.CreateSecurityTokenService(),
- System.Web.HttpContext.Current.Response);
- return View();
- }
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)方法修改如下:
- [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);
- }
[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方法修改如下:
- public ActionResult LogOff()
- {
- FormsAuthentication.SignOut();
- ViewData["AddressesExpected"] = SingleSignOnManager.SignOut().Distinct().ToArray();
- return View("Login");
- }
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
ViewData["AddressesExpected"] = SingleSignOnManager.SignOut().Distinct().ToArray();
return View("Login");
}
打开/Views/Account/Login.cshtml,添加以下代码:
- @{
- 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;" />
- }
- }
- }
@{
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文件,添加几个站点:
- 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
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里添加如下节点:
- <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的程序集:
- <compilation debug="true" targetFramework="4.5" >
- <assemblies>
- <add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
- </assemblies>
- </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节点,禁止匿名用户访问:
- <authentication mode="None">
- </authentication>
- <authorization>
- <deny users="?" />
- </authorization>
<authentication mode="None">
</authentication>
<authorization>
<deny users="?" />
</authorization>
添加三个httpModules:
- <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>
<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节点:
- <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>
<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和生日:
- @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>
@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:
- /// <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);
- }
- }
/// <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里加入这个类的配置:
- <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 —— 正式实战 -摘自网络的更多相关文章
- 使用WIF实现单点登录Part III —— 正式实战
我们接下来的demo将包括以下的工程: SiteA —— 基于.net framework 4.5的MVC 4程序,使用WIF 4.5的SDK,第一个RP SiteB —— 基于.net framew ...
- ABP集成WIF实现单点登录
ABP集成WIF实现单点登录 参考 ojlovecd写了三篇关于WIF文章. 使用WIF实现单点登录Part III —— 正式实战 使用WIF的一些开源示例. https://github.com/ ...
- 使用WIF实现单点登录Part IV —— 常见问题
InvalidOperationException: ID1073: 尝试使用 ProtectedData API 解密 Cookie 时出现 CryptographicException (有关详细 ...
- 使用WIF实现单点登录Part II —— Windows Identity Foundation基本原理
在上一篇文章中,我们已经使用WIF构建了一个基于MVC4的简单的身份验证程序,在这篇文章里,我们将探讨一下到底什么是WIF,以及它的工作原理.然后在下一篇文章开始,我们将实际操作,实现单点登录功能. ...
- 使用WIF实现单点登录Part II —— Windows Identity Foundation基本原理 -摘自网络
在上一篇文章中,我们已经使用WIF构建了一个基于MVC4的简单的身份验证程序,在这篇文章里,我们将探讨一下到底什么是WIF,以及它的工作原理.然后在下一篇文章开始,我们将实际操作,实现单点登录功能. ...
- 使用WIF实现单点登录Part I——Windows Identity Foundation介绍及环境搭建 -摘自网络
上个月有一个星期的时间都在研究asp.net mvc统一身份验证及单点登录的实现.经过了一番的探索,最终决定使用微软的Windows Identity Foundation.但是这东西用的人貌似不多, ...
- 使用WIF实现单点登录Part I——Windows Identity Foundation介绍及环境搭建
首先先说一下什么是WIF(Windows Identity Foundation).由于各种历史原因,身份验证和标识的管理一般都比较无规律可循.在软件里加入“身份验证”功能意味着要在你的代码里混进处理 ...
- 实战:ADFS3.0单点登录系列-集成MVC
本文将讲解如何让MVC应用程序与ADFS集成,完成认证的过程. 目录: 实战:ADFS3.0单点登录系列-总览 实战:ADFS3.0单点登录系列-前置准备 实战:ADFS3.0单点登录系列-ADFS3 ...
- 实战:ADFS3.0单点登录系列-总览
本系列将以一个实际项目为背景,介绍如何使用ADFS3.0实现SSO.其中包括SharePoint,MVC,Exchange等应用程序的SSO集成. 整个系列将会由如下几个部分构成: 实战:ADFS3. ...
随机推荐
- poj 1348 Computing (四个数的加减乘除四则运算)
http://poj.org/problem?id=1348 Computing Time Limit: 1000MS Memory Limit: 10000K Total Submissions ...
- 关于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 ...
- nodejs 第一次使用
在win7下安装与使用 1 nodejs官网下载,安装 https://nodejs.org/ 2 下载最新的 npm,在E:\nodejs\中解压 http://nodejs.org/dist/ ...
- Oracle删除表、字段之前判断表、字段是否存在
这篇文章主要介绍了Oracle删除表.字段之前判断表.字段是否存在的相关资料,需要的朋友可以参考下 在Oracle中若删除一个不存在的表,如 “DROP TABLE tableName”,则会提示: ...
- JS获取IP、MAC和主机名的几种方法
方法一(只针对IE且客户端的IE允许AcitiveX运行,通过平台:XP,SERVER03,2000): 获取客户端IP. <HTML> <HEAD> <TITLE> ...
- WEB黑客工具箱之LiveHttpHeaders介绍
一.LiveHttpHeaders之安装 自行百度 二.LiveHttpHeaders主窗口 根据我们目的的不同,LiveHttpHeaders有两种启动方法:当我们只想监视通信量的时候,可以从浏览器 ...
- codeforces 388C Fox and Card Game
刚刚看到这个题感觉是博弈题: 不过有感觉不像,应该是个贪心: 于是就想贪心策略: 举了一个例子: 3 3 1 2 3 4 3 4 1 2 5 4 1 2 5 8 如果他们两个每次都拿对自己最有利的那个 ...
- linux下安装jira详细步骤
首先从官网下载jdk的安装包,将jdk的安装包上传到虚拟机或者服务器,在./usr/local/目录下面创建一个java目录:mkdir java 等等,具体祥看本文,希望对你有所帮助 linux下安 ...
- 转载爱哥自定义View系列--Canvas详解
上面所罗列出来的各种drawXXX方法就是Canvas中定义好的能画什么的方法(drawPaint除外),除了各种基本型比如矩形圆形椭圆直曲线外Canvas也能直接让我们绘制各种图片以及颜色等等,但是 ...
- 公司估值(贴现现金流量法DCF)
创业公司总会遇到并购或者入股等情况,CEO需要了解一些公司估值的方法,本文主要介绍贴现现金流量估值方法,供大家参考: 中国资产评估协会要求:在对企业价值进行评估时,应分析收益法.市场法和资产基础法三种 ...