创建为.Net Auth Cookie交换自定义令牌的Owin Auth Provider

我正在尝试在2 .Net应用程序之间创建类似SSO的解决方案.Net app 1有一个自定义令牌生成器和端点来validation返回用户信息的令牌。

.Net应用程序2使用Owin进行保护,是一个典型的独立应用程序,用户可以使用密码和用户名直接登录。

我创建(基于Passion for Coding Blog和Github )一个自定义Owin提供程序,它可以在Authorization标头中查找标记,也可以从用户从.Net App 1单击链接并发送到的链接中查找参数.Net App 2查询字符串中的令牌与GET一样(我知道这不安全,我们最终将使用OpenID来获取它的价值,我们只需要这个演示)。 我能够获得令牌validation它并创建一个身份并进行身份validation我不能让提供者创建一个.Net Auth Cookie,以便后续请求得到身份validation而不会出现401错误。

处理程序文件:

using Microsoft.Owin.Infrastructure; using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Infrastructure; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace SomeOAuth { // Created by the factory in the someAuthenticationMiddleware class. class SomeAuthenticationHandler : AuthenticationHandler { private const string HandledResponse = "HandledResponse"; private readonly ILogger _logger; private readonly string _challenge; ///  /// Creates a new OpenIdConnectAuthenticationHandler ///  ///  public SomeAuthenticationHandler(ILogger logger, string challenge) { _logger = logger; _challenge = challenge; } protected override async Task AuthenticateCoreAsync() { // ASP.Net Identity requires the NameIdentitifer field to be set or it won't // accept the external login (AuthenticationManagerExtensions.GetExternalLoginInfo) string requestToken = null; string authorization = Request.Headers.Get("Authorization"); if (!string.IsNullOrEmpty(authorization)) { if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { requestToken = authorization.Substring("Bearer ".Length).Trim(); } } if (string.IsNullOrEmpty(requestToken)) { string accessTokenParam = Request.Query.Get("access_token"); if (!string.IsNullOrEmpty(accessTokenParam)) { requestToken = accessTokenParam; } } if (!string.IsNullOrEmpty(requestToken)) { using (var client = new HttpClient()) { try { var request = new HttpRequestMessage(HttpMethod.Post, "https://testserver/API/Auth/Authenticate"); var s = new StringContent("{\"oauthtoken\":\"" + requestToken + "\"}", Encoding.UTF8, "application/json"); // var ts = s.ToString(); request.Content = new StringContent("{\"oauthtoken\":\"" + requestToken + "\"}", Encoding.UTF8, "application/json"); System.Diagnostics.Debug.WriteLine("Request:"); System.Diagnostics.Debug.WriteLine(request.ToString()); if (request.Content != null) { System.Diagnostics.Debug.WriteLine(await request.Content.ReadAsStringAsync()); } System.Diagnostics.Debug.WriteLine(""); var response = await client.SendAsync(request); if (response.StatusCode != HttpStatusCode.OK) { return null; } var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); var userId = payload.Value("username"); //need to get the useid of the user as well as the name and role var identity = new ClaimsIdentity("Some"); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "fakeuser", null, "Some")); /* identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName + " " + user.LastName)); identity.AddClaim(new Claim(ClaimTypes.Email, user.ContactInfo.Email)); identity.AddClaim(new Claim(ClaimTypes.Sid, user.Guid.ToString())); */ identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Some")); AuthenticationProperties properties = CreateProperties("fakeusername", ""); var ticket = new AuthenticationTicket(identity, new AuthenticationProperties()); return ticket; } catch (Exception e) { Console.WriteLine("asdf e = " + e.Message); } return null; } } else { return null; } } ///  /// Handles SignIn ///  ///  protected override Task ApplyResponseChallengeAsync() { if (Response.StatusCode == 401) { AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); if (challenge == null) { return null; } } return Task.FromResult(null); } public override Task InvokeAsync() { return InvokeReplyPathAsync(); } private async Task InvokeReplyPathAsync() { AuthenticationTicket ticket = await AuthenticateAsync(); if (ticket != null) { string value; if (ticket.Properties.Dictionary.TryGetValue(HandledResponse, out value) && value == "true") { return true; } if (ticket.Identity != null) { Request.Context.Authentication.SignIn(ticket.Properties, ticket.Identity); } // Redirect back to the original secured resource, if any. if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri)) { Response.Redirect(ticket.Properties.RedirectUri); return true; } } return false; } private static AuthenticationTicket GetHandledResponseTicket() { return new AuthenticationTicket(null, new AuthenticationProperties(new Dictionary() { { HandledResponse, "true" } })); } public AuthenticationProperties CreateProperties(string userName, string Roles) { IDictionary data = new Dictionary { { "userName", userName }, {"roles",Roles} }; return new AuthenticationProperties(data); } } } 

中间件文件:

 using Microsoft.Owin; using Microsoft.Owin.Security.Infrastructure; using Owin; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Logging; namespace SomeOAuth { // One instance is created when the application starts. public class SomeeAuthenticationMiddleware : AuthenticationMiddleware { private readonly ILogger _logger; private readonly string _challenge = "Bearer"; public SomeAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SomeAuthenticationOptions options) : base(next, options) { _logger = app.CreateLogger(); } // Called for each request, to create a handler for each request. protected override AuthenticationHandler CreateHandler() { return new SomeAuthenticationHandler(_logger, _challenge); } } } 

选项文件:

 using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.OAuth; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SomeOAuth { public class SomeAuthenticationOptions : AuthenticationOptions { public SomeAuthenticationOptions(string userName, string userId) : base(OAuthDefaults.AuthenticationType) { UserName = userName; UserId = userId; } public string Challenge { get; set; } public string UserName { get; set; } public string UserId { get; set; } } } 

扩展文件:

 using Microsoft.Owin.Extensions; using Owin; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SomeOAuth { public static class SomeAuthenticationExtensions { public static IAppBuilder UseSomeeAuthentication(this IAppBuilder app, SomeAuthenticationOptions options) { if (app == null) { throw new ArgumentNullException("app"); } app.Use(typeof(SomeAuthenticationMiddleware), app, options); app.UseStageMarker(PipelineStage.Authenticate); return app; } } } 

启动文件

 using System; using CoreLX.Palms.VS.Web.Services; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin.Security.Providers.OpenID; using Microsoft.Owin.Security.OAuth; using Owin; using SomeOAuth; using CoreLX.Palms.LS.Web.Common.Models.User; namespace CoreLX.Palms.VS.Web { public partial class Startup { public void ConfigureAuth(IAppBuilder app) { app.CreatePerOwinContext(ApplicationUserManager.Create); app.CreatePerOwinContext (ApplicationSignInManager.Create); //app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions //{ // AccessTokenProvider = new SomeTokenProvider(), // Provider = new SomeOAuthBearerAuthenticationProvider("access_token") //}); app.UseSomeAuthentication(new SomeAuthenticationOptions("testuser", "9")); // Use a cookie to temp store information about a user logging in with a third party login provider app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Enable the application to use a cookie to store information for the signed in user app.UseCookieAuthentication( new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), ExpireTimeSpan = new TimeSpan(0, 3, 0, 0), Provider = new CookieAuthenticationProvider { OnValidateIdentity = SecurityStampValidator.OnValidateIdentity( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)), OnApplyRedirect = ctx => { // don't redirect to login page for webapi/ajax requests // http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/ if (!IsWebApiRequest(ctx.Request)) { ctx.Response.Redirect(ctx.RedirectUri); } } } }); app.UseOpenIDAuthentication("http://me.yahoo.com/", "Yahoo"); } private static bool IsWebApiRequest(IOwinRequest request) { // hack for check if it's webapi requesr if (request.Path.StartsWithSegments(new PathString("/api"))) { return true; } // checks if it's ajax request IReadableStringCollection query = request.Query; if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest")) { return true; } IHeaderDictionary headers = request.Headers; return ((headers != null) && (headers["X-Requested-With"] == "XMLHttpRequest")); } } } 

我也尝试过使用自定义提供程序来提供标准

 OAuthBearerAuthenticationProvider 

以下是我尝试的插件/提供程序的代码,只要没有401错误,我没有偏好:

提供商

 using Microsoft.Owin.Security.OAuth; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SomeOAuth { public class SomeOAuthBearerAuthenticationProvider : IOAuthBearerAuthenticationProvider { readonly string _parameterName; public SomeOAuthBearerAuthenticationProvider(string parameterName) { _parameterName = parameterName; } public Task ApplyChallenge(OAuthChallengeContext context) { return Task.FromResult(null); } public Task RequestToken(OAuthRequestTokenContext context) { string token = context.Token; if(string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(_parameterName)) { token = context.Request.Query.Get(_parameterName); } if (!string.IsNullOrEmpty(token)) { context.Token = token; } return Task.FromResult(null); } public Task ValidateIdentity(OAuthValidateIdentityContext context) { context.Validated(); return Task.FromResult(null); } } } 

和AccessTokenProvider

 using Microsoft.Owin.Security; using Microsoft.Owin.Security.Infrastructure; using Newtonsoft.Json.Linq; using System; //using Newtonsoft.Json.Linq; using System.Net; using System.Net.Http; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace SomeOAuth { public sealed class SomeTokenProvider : AuthenticationTokenProvider { public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { using (var client = new HttpClient()) { try { var request = new HttpRequestMessage(HttpMethod.Post, "https://someserver/API/Auth/Authenticate"); var s = new StringContent("{\"oauthtoken\":\"" + context.Token + "\"}", Encoding.UTF8, "application/json"); // var ts = s.ToString(); request.Content = new StringContent("{\"oauthtoken\":\"" + context.Token + "\"}", Encoding.UTF8, "application/json"); System.Diagnostics.Debug.WriteLine("Request:"); System.Diagnostics.Debug.WriteLine(request.ToString()); if (request.Content != null) { System.Diagnostics.Debug.WriteLine(await request.Content.ReadAsStringAsync()); } System.Diagnostics.Debug.WriteLine(""); var response = await client.SendAsync(request); if (response.StatusCode != HttpStatusCode.OK) { return; } var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); var userId = payload.Value("username"); //need to get the useid of the user as well as the name and role var identity = new ClaimsIdentity("Some"); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "someuser", null, "Some")); /* identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName + " " + user.LastName)); identity.AddClaim(new Claim(ClaimTypes.Email, user.ContactInfo.Email)); identity.AddClaim(new Claim(ClaimTypes.Sid, user.Guid.ToString())); */ identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Some")); context.SetTicket(new AuthenticationTicket(identity, new AuthenticationProperties())); } catch (Exception e) { Console.WriteLine("asdf e = " + e.Message); } } } } } 

您正在以错误的顺序注册中间件。 owin中间件模型通过auth中间件工作,在返回到先前的中间件之前在owin字典中放置一条指令( AuthenticationResponseGrant )。 如果以前的中间件是外部cookie中间件,它将发出cookie。 我的博文中有更详细的内容 。 所以切换这两行:

 // Use a cookie to temp store information about a user logging in with a third party login provider app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); app.UseSomeAuthentication(new SomeAuthenticationOptions("testuser", "9")); 

还有另一个问题。 AuthenticationType必须是一个cookie中间件。 UseExternalSignInCookie方法在内部调用app.SetDefaultSignInAsAuthenticationType因此在创建新的ClaimsIdentity ,不应将Some用作身份validation类型,而应通过options类上的app.GetDefaultSignInAsAuthenticationType()传递app.GetDefaultSignInAsAuthenticationType()的结果。 对app.GetDefaultSignInAsAuthenticationType()的调用通常在中间件构造函数中完成。