我正在尝试在 2 个 .Net 应用程序之间创建类似 SSO 的解决方案。Net 应用程序 1 有一个自定义令牌生成器和端点来验证返回用户信息的令牌。
.Net 应用程序 2 使用 Owin 进行保护,是一个典型的独立应用程序,用户可以使用密码和用户名直接登录。
我创建(基于Passion for Coding Blog和Github)一个自定义 Owin 提供程序,它将在 Authorization 标头中查找令牌或作为来自链接的查询参数,用户将单击来自 .Net App 1 的链接并发送到.Net App 2 查询字符串中的令牌,如 GET (我知道这不安全,我们最终将使用 OpenID 来实现它的价值,我们只需要它来进行演示)。我能够让令牌验证它并创建一个身份并进行身份验证我只是无法让提供者创建一个.Net Auth Cookie,以便对后续请求进行身份验证并且不会给出 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<SomeAuthenticationOptions>
{
private const string HandledResponse = "HandledResponse";
private readonly ILogger _logger;
private readonly string _challenge;
/// <summary>
/// Creates a new OpenIdConnectAuthenticationHandler
/// </summary>
/// <param name="logger"></param>
public SomeAuthenticationHandler(ILogger logger, string challenge)
{
_logger = logger;
_challenge = challenge;
}
protected override async Task<AuthenticationTicket> 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<string>("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;
}
}
/// <summary>
/// Handles SignIn
/// </summary>
/// <returns></returns>
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge == null)
{
return null;
}
}
return Task.FromResult<object>(null);
}
public override Task<bool> InvokeAsync()
{
return InvokeReplyPathAsync();
}
private async Task<bool> 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<string, string>() { { HandledResponse, "true" } }));
}
public AuthenticationProperties CreateProperties(string userName, string Roles)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "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<SomeAuthenticationOptions>
{
private readonly ILogger _logger;
private readonly string _challenge = "Bearer";
public SomeAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SomeAuthenticationOptions options)
: base(next, options)
{
_logger = app.CreateLogger<SomeAuthenticationMiddleware>();
}
// Called for each request, to create a handler for each request.
protected override AuthenticationHandler<SomeAuthenticationOptions> 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>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager> (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<ApplicationUserManager, ApplicationUser>(
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<object>(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<object>(null);
}
public Task ValidateIdentity(OAuthValidateIdentityContext context)
{
context.Validated();
return Task.FromResult<object>(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<string>("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);
}
}
}
}
}