带 CAS 的 OWIN 中间件
OWIN Middleware with CAS
我正在尝试实现重定向到 CAS 服务器(中央身份验证服务)的自定义 OWIN 中间件身份验证。一位同事构建了中间件,它似乎大部分都在工作,但我们都不知道如何在中间件成功验证用户身份后存储 cookie 并重定向到 AccountController 上的 ExternalCallbackLogin,以便允许用户访问受保护的内容。
程序流程应遵循位于以下位置的 Web 流程图:Jasig CAS Webflow Diagram
我正按预期重定向到我们的内部 CAS 服务器,当我登录时,我能够检索服务器提供的 XML 以生成声明,但是
从这里我不知道如何创建应用程序 cookie 并访问中间件之外的声明。
我们没有在这个应用程序中实现 ASP.Net 身份,我们按照这个 tutorial 将 CAS 设置为唯一的登录选项。我们无意允许其他外部登录。
如有任何帮助,我们将不胜感激。如果我需要提供更多信息,我会很乐意。
下面是中间件的全部代码:
CasOptions.cs
using Microsoft.Owin.Security;
using System;
namespace owin.cas.client {
public class CasOptions : AuthenticationOptions {
private string _casVersion;
private string _callbackPath;
public CasOptions() : base(Constants.AuthenticationType) {
this.AuthenticationMode = AuthenticationMode.Passive;
this.AuthenticationType = Constants.AuthenticationType; // Default is owin.cas.client
this.callbackPath = "/casHandler";
this.casVersion = "3";
this.Caption = Constants.AuthenticationType;
}
/// <summary>
/// The local URI path that will handle callbacks from the remote CAS server. The default is "/casHandler".
/// </summary>
/// <value>The callback path.</value>
public string callbackPath
{
get{ return this._callbackPath; }
set{
if (value.StartsWith("/", StringComparison.InvariantCulture)){
this._callbackPath = value;
}
else
{
this._callbackPath = "/" + value;
}
}
}
/// <summary>
/// This must be the base URL for your application as it is registered with the remote CAS server, minus the
/// callback path. For example, if your service is registered as "https://example.com/casHandler" with the
/// remote CAS server then you would set this property to "https://example.com".
/// </summary>
/// <value>The application URL.</value>
public string applicationURL { get; set; }
/// <summary>
/// This must be set to the base URL for the remote CAS server. For example, if the remote CAS server's login
/// URL is "https://cas.example.com/login" you would set this value to "https://cas.example.com".
/// </summary>
/// <value>The cas base URL.</value>
public string casBaseUrl {
get { return this._casVersion; }
set {
this._casVersion = value.TrimEnd('/');
}
}
public string Caption
{
get { return Description.Caption; }
set { Description.Caption = value; }
}
/// <summary>
/// Set to the CAS protocol version the remote CAS server supports. The default is "3". Acceptable values
/// are "1", "2", or "3".
/// </summary>
/// <value>The cas version.</value>
public string casVersion { get; set; }
// Used to store the Url that requires authentication. Typically marked by an Authorize tag.
public string externalRedirectUrl { get; set; }
}
}
CasMiddleware.cs
using Microsoft.Owin;
using Microsoft.Owin.Security.Infrastructure;
using Owin;
using System.Net.Http;
using System.Configuration;
namespace owin.cas.client {
public class CasMiddleware : AuthenticationMiddleware<CasOptions> {
private readonly HttpClient httpClient;
private readonly ICasCommunicator casCommunicator;
public CasMiddleware(OwinMiddleware next, IAppBuilder app, CasOptions options) : base(next, options) {
if (string.IsNullOrEmpty(options.casBaseUrl)) {
throw new SettingsPropertyNotFoundException("Missing required casBaseUrl option.");
}
if (string.IsNullOrEmpty(options.applicationURL)) {
throw new SettingsPropertyNotFoundException("Missing required serviceUrl option.");
}
this.httpClient = new HttpClient();
switch (options.casVersion) {
case "1":
this.casCommunicator = new Cas10(this.httpClient, options);
break;
case "3":
this.casCommunicator = new Cas30(this.httpClient, options);
break;
}
}
protected override AuthenticationHandler<CasOptions> CreateHandler() {
return new CasHandler(this.casCommunicator);
}
}
}
CasHandler.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using System.Security.Claims;
namespace owin.cas.client {
public class CasHandler : AuthenticationHandler<CasOptions> {
private readonly ICasCommunicator casCommunicator;
public CasHandler(ICasCommunicator casCommunicator) {
this.casCommunicator = casCommunicator;
}
public override async Task<bool> InvokeAsync() {
// Handle the callback from the remote CAS server
if (this.Request.Path.ToString().Equals(this.Options.callbackPath)) {
return await this.InvokeCallbackAsync();
}
// Let the next middleware do its thing instead.
return false;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync() {
IReadableStringCollection query = Request.Query;
IList<string> tickets = query.GetValues("ticket");
string ticket = (tickets.Count == 1) ? tickets[0] : null;
if (string.IsNullOrEmpty(ticket)) {
return new AuthenticationTicket(null, new AuthenticationProperties());
}
CasIdentity casIdentity = await this.casCommunicator.validateTicket(ticket);
return new AuthenticationTicket(casIdentity, casIdentity.authenticationProperties);
}
protected override Task ApplyResponseChallengeAsync() {
if (Response.StatusCode != 401) {
return Task.FromResult<object>(null);
}
AuthenticationResponseChallenge challenge = this.Helper.LookupChallenge(this.Options.AuthenticationType, this.Options.AuthenticationMode);
if (challenge != null) {
string authUrl = this.Options.casBaseUrl + "/login?service=" + Uri.EscapeUriString(this.Options.applicationURL + this.Options.callbackPath);
this.Options.externalRedirectUrl = challenge.Properties.RedirectUri;
this.Response.StatusCode = 302;
this.Response.Headers.Set("Location", authUrl);
}
return Task.FromResult<object>(null);
}
// Basically the same thing as InvokereplyPathAsync() found in most
// middleware
protected async Task<bool> InvokeCallbackAsync() {
AuthenticationTicket authenticationTicket = await this.AuthenticateAsync();
if (authenticationTicket == null) {
this.Response.StatusCode = 500;
this.Response.Write("Invalid authentication ticket.");
return true;
}
// this.Context.Authentication.SignIn(authenticationTicket.Identity);
this.Context.Authentication.SignIn(authenticationTicket.Properties, authenticationTicket.Identity);
if(this.Options.externalRedirectUrl != null) {
Response.Redirect(this.Options.externalRedirectUrl);
}
return true;
}
}
}
Constants.cs
using System;
namespace owin.cas.client {
internal static class Constants {
internal const string AuthenticationType = "owin.cas.client";
internal const string V1_VALIDATE = "/validate";
internal const string V2_VALIDATE = "/serviceValidate";
internal const string V3_VALIDATE = "/p3/serviceValidate";
}
}
ICasCommunicator.cs
using System;
using System.Threading.Tasks;
namespace owin.cas.client {
public interface ICasCommunicator {
Task<CasIdentity> validateTicket(string ticket);
}
}
CasIdentity.cs
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.Owin.Security;
namespace owin.cas.client {
public class CasIdentity : ClaimsIdentity {
public CasIdentity() : base() { }
public CasIdentity(IList<Claim> claims) : base(claims) { }
public CasIdentity(IList<Claim> claims, string authType) : base(claims, authType) { }
public AuthenticationProperties authenticationProperties { get; set; }
}
}
CasExtensions.cs
using Owin;
using Microsoft.Owin.Extensions;
namespace owin.cas.client {
public static class CasExtensions {
public static IAppBuilder UseCasAuthentication(this IAppBuilder app, CasOptions options) {
app.Use(typeof(CasMiddleware), app, options);
app.UseStageMarker(PipelineStage.Authenticate);
return app;
}
}
}
Cas30.cs(对应Jasig CAS当前协议版本)
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Security.Claims;
using Microsoft.Owin.Security;
using System.Xml;
using XML;
using System.Collections.Generic;
using System.Diagnostics;
namespace owin.cas.client {
public class Cas30 : ICasCommunicator {
private readonly HttpClient httpClient;
private readonly CasOptions options;
public Cas30(HttpClient httpClient, CasOptions options) {
this.httpClient = httpClient;
this.options = options;
}
public async Task<CasIdentity> validateTicket(string ticket) {
CasIdentity result = new CasIdentity();
HttpResponseMessage response = await this.httpClient.GetAsync(
this.options.casBaseUrl + Constants.V3_VALIDATE +
"?service=" + Uri.EscapeUriString(this.options.applicationURL + this.options.callbackPath) +
"&ticket=" + Uri.EscapeUriString(ticket)
);
string httpResult = await response.Content.ReadAsStringAsync();
XmlDocument xml = XML.Documents.FromString(httpResult);
//Begin modification
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("cas", "http://www.yale.edu/tp/cas");
if (xml.GetElementsByTagName("cas:authenticationFailure").Count > 0) {
result = new CasIdentity();
result.authenticationProperties = new AuthenticationProperties();
} else {
IList<Claim> claims = new List<Claim>();
string username = xml.SelectSingleNode("//cas:user", nsmgr).InnerText;
claims.Add(new Claim(ClaimTypes.Name, username));
claims.Add(new Claim(ClaimTypes.NameIdentifier, username));
XmlNodeList xmlAttributes = xml.GetElementsByTagName("cas:attributes");
AuthenticationProperties authProperties = new AuthenticationProperties();
if (xmlAttributes.Count > 0){
foreach (XmlElement attr in xmlAttributes) {
if (attr.HasChildNodes) {
for (int i = 0; i < attr.ChildNodes.Count; i++) {
switch (attr.ChildNodes[i].Name)
{
case "cas:authenticationDate":
authProperties.Dictionary.Add(attr.ChildNodes[i].Name, DateTime.Parse(attr.ChildNodes[i].InnerText).ToString());
break;
case "cas:longTermAuthenticationRequestTokenUsed":
case "cas:isFromNewLogin":
authProperties.Dictionary.Add(attr.ChildNodes[i].Name, Boolean.Parse(attr.ChildNodes[i].InnerText).ToString());
break;
case "cas:memberOf":
claims.Add(new Claim(ClaimTypes.Role, attr.ChildNodes[i].InnerText));
break;
default:
authProperties.Dictionary.Add(attr.ChildNodes[i].Name, attr.ChildNodes[i].InnerText);
break;
}
}
}
}
result = new CasIdentity(claims, this.options.AuthenticationType);
}
result.authenticationProperties = authProperties;
}
return result;
}
}
}
Startup.Auth.cs
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using owin.cas.client;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
[assembly: OwinStartup(typeof(TestCASapp.Startup))] // This specifies the startup class.
namespace TestCASapp
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions
{
LoginPath = new PathString("/Account/Login"),
};
app.UseCookieAuthentication(cookieOptions);
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType = "owin.cas.client");
CasOptions casOptions = new CasOptions();
casOptions.applicationURL = "http://www.yourdomain.com/TestCASapp"; // The application URL registered with the CAS server minus the callback path, in this case /casHandler
casOptions.casBaseUrl = "https://devcas.int.*****.com"; // The base url of the remote CAS server you are targeting for login.
casOptions.callbackPath = "/casHandler"; // Callback path picked up by the middleware to begin the authentication ticket process
casOptions.AuthenticationMode = AuthenticationMode.Passive;
app.UseCasAuthentication(casOptions);
}
}
}
账户控制器
using Microsoft.Owin.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace TestCASapp.Controllers
{
public class AccountController : Controller
{
public ActionResult Login(string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult("owin.cas.client",
Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
//return new ChallengeResult("Google",
// Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
public ActionResult ExternalLoginCallback(string returnUrl)
{
return new RedirectResult(returnUrl);
}
// Implementation copied from a standard MVC Project, with some stuff
// that relates to linking a new external login to an existing identity
// account removed.
private class ChallengeResult : HttpUnauthorizedResult
{
public ChallengeResult(string provider, string redirectUri)
{
LoginProvider = provider;
RedirectUri = redirectUri;
}
public string LoginProvider { get; set; }
public string RedirectUri { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
}
}
}
Startup.cs
using Microsoft.Owin;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
[assembly: OwinStartupAttribute(typeof(TestCASapp.Startup))]
namespace TestCASapp
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
我弄清楚是什么导致中间件不存储 cookie 信息了。在"Using Owin External Login without Identity"一文中找到了下面这句话的解决方法,"The cookie middleware will only issue a cookie if the AuthenticationType matches the one in the identity created by the social login middleware."
当我发布问题时,我将 cookie 中间件身份验证类型设置为其默认值 属性,如果我没记错的话,它是 "ApplicationCookie"。但是,我需要将身份验证类型设置为 "owin.cas.client" 以使其与外部登录中间件创建的身份相匹配。相应地设置后,我的应用程序开始按预期设置 cookie。
我遇到的另一个问题是中间件没有重定向到帐户控制器上的 ExternalLoginCallback。这是因为我没有在 CasMiddleware 中保存调用 ChallangeResult class 时创建的 redirectUrl。我将 RedirectUrl 添加到 CasOptions class,然后,一旦身份验证完成,我只是将用户重定向回需要身份验证的页面。
我已经更新了我原来的问题以反映我的变化,希望这能在将来证明对其他人有益。
我正在尝试实现重定向到 CAS 服务器(中央身份验证服务)的自定义 OWIN 中间件身份验证。一位同事构建了中间件,它似乎大部分都在工作,但我们都不知道如何在中间件成功验证用户身份后存储 cookie 并重定向到 AccountController 上的 ExternalCallbackLogin,以便允许用户访问受保护的内容。
程序流程应遵循位于以下位置的 Web 流程图:Jasig CAS Webflow Diagram
我正按预期重定向到我们的内部 CAS 服务器,当我登录时,我能够检索服务器提供的 XML 以生成声明,但是 从这里我不知道如何创建应用程序 cookie 并访问中间件之外的声明。
我们没有在这个应用程序中实现 ASP.Net 身份,我们按照这个 tutorial 将 CAS 设置为唯一的登录选项。我们无意允许其他外部登录。
如有任何帮助,我们将不胜感激。如果我需要提供更多信息,我会很乐意。
下面是中间件的全部代码:
CasOptions.cs
using Microsoft.Owin.Security;
using System;
namespace owin.cas.client {
public class CasOptions : AuthenticationOptions {
private string _casVersion;
private string _callbackPath;
public CasOptions() : base(Constants.AuthenticationType) {
this.AuthenticationMode = AuthenticationMode.Passive;
this.AuthenticationType = Constants.AuthenticationType; // Default is owin.cas.client
this.callbackPath = "/casHandler";
this.casVersion = "3";
this.Caption = Constants.AuthenticationType;
}
/// <summary>
/// The local URI path that will handle callbacks from the remote CAS server. The default is "/casHandler".
/// </summary>
/// <value>The callback path.</value>
public string callbackPath
{
get{ return this._callbackPath; }
set{
if (value.StartsWith("/", StringComparison.InvariantCulture)){
this._callbackPath = value;
}
else
{
this._callbackPath = "/" + value;
}
}
}
/// <summary>
/// This must be the base URL for your application as it is registered with the remote CAS server, minus the
/// callback path. For example, if your service is registered as "https://example.com/casHandler" with the
/// remote CAS server then you would set this property to "https://example.com".
/// </summary>
/// <value>The application URL.</value>
public string applicationURL { get; set; }
/// <summary>
/// This must be set to the base URL for the remote CAS server. For example, if the remote CAS server's login
/// URL is "https://cas.example.com/login" you would set this value to "https://cas.example.com".
/// </summary>
/// <value>The cas base URL.</value>
public string casBaseUrl {
get { return this._casVersion; }
set {
this._casVersion = value.TrimEnd('/');
}
}
public string Caption
{
get { return Description.Caption; }
set { Description.Caption = value; }
}
/// <summary>
/// Set to the CAS protocol version the remote CAS server supports. The default is "3". Acceptable values
/// are "1", "2", or "3".
/// </summary>
/// <value>The cas version.</value>
public string casVersion { get; set; }
// Used to store the Url that requires authentication. Typically marked by an Authorize tag.
public string externalRedirectUrl { get; set; }
}
}
CasMiddleware.cs
using Microsoft.Owin;
using Microsoft.Owin.Security.Infrastructure;
using Owin;
using System.Net.Http;
using System.Configuration;
namespace owin.cas.client {
public class CasMiddleware : AuthenticationMiddleware<CasOptions> {
private readonly HttpClient httpClient;
private readonly ICasCommunicator casCommunicator;
public CasMiddleware(OwinMiddleware next, IAppBuilder app, CasOptions options) : base(next, options) {
if (string.IsNullOrEmpty(options.casBaseUrl)) {
throw new SettingsPropertyNotFoundException("Missing required casBaseUrl option.");
}
if (string.IsNullOrEmpty(options.applicationURL)) {
throw new SettingsPropertyNotFoundException("Missing required serviceUrl option.");
}
this.httpClient = new HttpClient();
switch (options.casVersion) {
case "1":
this.casCommunicator = new Cas10(this.httpClient, options);
break;
case "3":
this.casCommunicator = new Cas30(this.httpClient, options);
break;
}
}
protected override AuthenticationHandler<CasOptions> CreateHandler() {
return new CasHandler(this.casCommunicator);
}
}
}
CasHandler.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using System.Security.Claims;
namespace owin.cas.client {
public class CasHandler : AuthenticationHandler<CasOptions> {
private readonly ICasCommunicator casCommunicator;
public CasHandler(ICasCommunicator casCommunicator) {
this.casCommunicator = casCommunicator;
}
public override async Task<bool> InvokeAsync() {
// Handle the callback from the remote CAS server
if (this.Request.Path.ToString().Equals(this.Options.callbackPath)) {
return await this.InvokeCallbackAsync();
}
// Let the next middleware do its thing instead.
return false;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync() {
IReadableStringCollection query = Request.Query;
IList<string> tickets = query.GetValues("ticket");
string ticket = (tickets.Count == 1) ? tickets[0] : null;
if (string.IsNullOrEmpty(ticket)) {
return new AuthenticationTicket(null, new AuthenticationProperties());
}
CasIdentity casIdentity = await this.casCommunicator.validateTicket(ticket);
return new AuthenticationTicket(casIdentity, casIdentity.authenticationProperties);
}
protected override Task ApplyResponseChallengeAsync() {
if (Response.StatusCode != 401) {
return Task.FromResult<object>(null);
}
AuthenticationResponseChallenge challenge = this.Helper.LookupChallenge(this.Options.AuthenticationType, this.Options.AuthenticationMode);
if (challenge != null) {
string authUrl = this.Options.casBaseUrl + "/login?service=" + Uri.EscapeUriString(this.Options.applicationURL + this.Options.callbackPath);
this.Options.externalRedirectUrl = challenge.Properties.RedirectUri;
this.Response.StatusCode = 302;
this.Response.Headers.Set("Location", authUrl);
}
return Task.FromResult<object>(null);
}
// Basically the same thing as InvokereplyPathAsync() found in most
// middleware
protected async Task<bool> InvokeCallbackAsync() {
AuthenticationTicket authenticationTicket = await this.AuthenticateAsync();
if (authenticationTicket == null) {
this.Response.StatusCode = 500;
this.Response.Write("Invalid authentication ticket.");
return true;
}
// this.Context.Authentication.SignIn(authenticationTicket.Identity);
this.Context.Authentication.SignIn(authenticationTicket.Properties, authenticationTicket.Identity);
if(this.Options.externalRedirectUrl != null) {
Response.Redirect(this.Options.externalRedirectUrl);
}
return true;
}
}
}
Constants.cs
using System;
namespace owin.cas.client {
internal static class Constants {
internal const string AuthenticationType = "owin.cas.client";
internal const string V1_VALIDATE = "/validate";
internal const string V2_VALIDATE = "/serviceValidate";
internal const string V3_VALIDATE = "/p3/serviceValidate";
}
}
ICasCommunicator.cs
using System;
using System.Threading.Tasks;
namespace owin.cas.client {
public interface ICasCommunicator {
Task<CasIdentity> validateTicket(string ticket);
}
}
CasIdentity.cs
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.Owin.Security;
namespace owin.cas.client {
public class CasIdentity : ClaimsIdentity {
public CasIdentity() : base() { }
public CasIdentity(IList<Claim> claims) : base(claims) { }
public CasIdentity(IList<Claim> claims, string authType) : base(claims, authType) { }
public AuthenticationProperties authenticationProperties { get; set; }
}
}
CasExtensions.cs
using Owin;
using Microsoft.Owin.Extensions;
namespace owin.cas.client {
public static class CasExtensions {
public static IAppBuilder UseCasAuthentication(this IAppBuilder app, CasOptions options) {
app.Use(typeof(CasMiddleware), app, options);
app.UseStageMarker(PipelineStage.Authenticate);
return app;
}
}
}
Cas30.cs(对应Jasig CAS当前协议版本)
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Security.Claims;
using Microsoft.Owin.Security;
using System.Xml;
using XML;
using System.Collections.Generic;
using System.Diagnostics;
namespace owin.cas.client {
public class Cas30 : ICasCommunicator {
private readonly HttpClient httpClient;
private readonly CasOptions options;
public Cas30(HttpClient httpClient, CasOptions options) {
this.httpClient = httpClient;
this.options = options;
}
public async Task<CasIdentity> validateTicket(string ticket) {
CasIdentity result = new CasIdentity();
HttpResponseMessage response = await this.httpClient.GetAsync(
this.options.casBaseUrl + Constants.V3_VALIDATE +
"?service=" + Uri.EscapeUriString(this.options.applicationURL + this.options.callbackPath) +
"&ticket=" + Uri.EscapeUriString(ticket)
);
string httpResult = await response.Content.ReadAsStringAsync();
XmlDocument xml = XML.Documents.FromString(httpResult);
//Begin modification
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("cas", "http://www.yale.edu/tp/cas");
if (xml.GetElementsByTagName("cas:authenticationFailure").Count > 0) {
result = new CasIdentity();
result.authenticationProperties = new AuthenticationProperties();
} else {
IList<Claim> claims = new List<Claim>();
string username = xml.SelectSingleNode("//cas:user", nsmgr).InnerText;
claims.Add(new Claim(ClaimTypes.Name, username));
claims.Add(new Claim(ClaimTypes.NameIdentifier, username));
XmlNodeList xmlAttributes = xml.GetElementsByTagName("cas:attributes");
AuthenticationProperties authProperties = new AuthenticationProperties();
if (xmlAttributes.Count > 0){
foreach (XmlElement attr in xmlAttributes) {
if (attr.HasChildNodes) {
for (int i = 0; i < attr.ChildNodes.Count; i++) {
switch (attr.ChildNodes[i].Name)
{
case "cas:authenticationDate":
authProperties.Dictionary.Add(attr.ChildNodes[i].Name, DateTime.Parse(attr.ChildNodes[i].InnerText).ToString());
break;
case "cas:longTermAuthenticationRequestTokenUsed":
case "cas:isFromNewLogin":
authProperties.Dictionary.Add(attr.ChildNodes[i].Name, Boolean.Parse(attr.ChildNodes[i].InnerText).ToString());
break;
case "cas:memberOf":
claims.Add(new Claim(ClaimTypes.Role, attr.ChildNodes[i].InnerText));
break;
default:
authProperties.Dictionary.Add(attr.ChildNodes[i].Name, attr.ChildNodes[i].InnerText);
break;
}
}
}
}
result = new CasIdentity(claims, this.options.AuthenticationType);
}
result.authenticationProperties = authProperties;
}
return result;
}
}
}
Startup.Auth.cs
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using owin.cas.client;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
[assembly: OwinStartup(typeof(TestCASapp.Startup))] // This specifies the startup class.
namespace TestCASapp
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions
{
LoginPath = new PathString("/Account/Login"),
};
app.UseCookieAuthentication(cookieOptions);
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType = "owin.cas.client");
CasOptions casOptions = new CasOptions();
casOptions.applicationURL = "http://www.yourdomain.com/TestCASapp"; // The application URL registered with the CAS server minus the callback path, in this case /casHandler
casOptions.casBaseUrl = "https://devcas.int.*****.com"; // The base url of the remote CAS server you are targeting for login.
casOptions.callbackPath = "/casHandler"; // Callback path picked up by the middleware to begin the authentication ticket process
casOptions.AuthenticationMode = AuthenticationMode.Passive;
app.UseCasAuthentication(casOptions);
}
}
}
账户控制器
using Microsoft.Owin.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace TestCASapp.Controllers
{
public class AccountController : Controller
{
public ActionResult Login(string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult("owin.cas.client",
Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
//return new ChallengeResult("Google",
// Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
public ActionResult ExternalLoginCallback(string returnUrl)
{
return new RedirectResult(returnUrl);
}
// Implementation copied from a standard MVC Project, with some stuff
// that relates to linking a new external login to an existing identity
// account removed.
private class ChallengeResult : HttpUnauthorizedResult
{
public ChallengeResult(string provider, string redirectUri)
{
LoginProvider = provider;
RedirectUri = redirectUri;
}
public string LoginProvider { get; set; }
public string RedirectUri { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
}
}
}
Startup.cs
using Microsoft.Owin;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
[assembly: OwinStartupAttribute(typeof(TestCASapp.Startup))]
namespace TestCASapp
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
我弄清楚是什么导致中间件不存储 cookie 信息了。在"Using Owin External Login without Identity"一文中找到了下面这句话的解决方法,"The cookie middleware will only issue a cookie if the AuthenticationType matches the one in the identity created by the social login middleware."
当我发布问题时,我将 cookie 中间件身份验证类型设置为其默认值 属性,如果我没记错的话,它是 "ApplicationCookie"。但是,我需要将身份验证类型设置为 "owin.cas.client" 以使其与外部登录中间件创建的身份相匹配。相应地设置后,我的应用程序开始按预期设置 cookie。
我遇到的另一个问题是中间件没有重定向到帐户控制器上的 ExternalLoginCallback。这是因为我没有在 CasMiddleware 中保存调用 ChallangeResult class 时创建的 redirectUrl。我将 RedirectUrl 添加到 CasOptions class,然后,一旦身份验证完成,我只是将用户重定向回需要身份验证的页面。
我已经更新了我原来的问题以反映我的变化,希望这能在将来证明对其他人有益。