在解决了asp.net core中访问memcached缓存的问题后,咱们开始大踏步地向.net core进军——将更多站点向asp.net core迁移,在迁移涉及获取用户登陆信息的站点时,咱们遇到了一个问题——如何在asp.net core与传统asp.net之间共享保存用户登陆信息的cookie?html
对于cookie的加解密,传统asp.net用的是对称加解密算法,而asp.net core用的是基于公钥私钥的非对称加解密算法,因此asp.net core没法解密传统asp.net生成的cookie,传统asp.net也没法解密asp.net core生成的cookie。针对于这个问题,.net社区已经有人提供了解决办法——让传统asp.net改用和asp.net core同样的加解密算法(详见这里),可是这须要修改全部涉及获取用户登陆信息的传统asp.net站点的代码,有些奢侈,不到万不得已,咱们不想采用,咱们要另辟蹊径。git
先简化一下问题,根据咱们向ASP.NET Core迁移过渡阶段的实际场景,用户登陆操做是在传统asp.net站点上完成的,咱们只需在asp.net core站点中解密cookie获取用户登陆信息便可,连加密都不须要。既然asp.net core本身解密不了,那可让传统asp.net帮忙解密,asp.net core将接收到的cookie经过web api发给传统asp.net解密。简化后问题变成了——在asp.net core中如何接收传统asp.net的cookie?如何拦截asp.net core对cookie的解密操做?传统asp.net如何在web api中从cookie中解密用户验证信息(FormsAuthenticationTicket)?在asp.net core中如何将FormsAuthenticationTicket转换为自身的验证信息(AuthenticationTicket)?咱们来逐一解决这些问题。github
问题一:在asp.net core中如何接收传统asp.net的cookie?web
这个问题很容易解决。只需在Startup.cs中将CookieAuthenticationOptions的CookieName与CookieDomain设置为与传统asp.net同样。算法
var cookieOptions = new CookieAuthenticationOptions { CookieName = ".CnblogsCookie", CookieDomain = ".cnblogs.com", }; app.UseCookieAuthentication(cookieOptions);
问题二:如何拦截asp.net core对cookie的解密操做?api
这个问题比较棘手。咱们是经过阅读 Microsoft.AspNetCore.Authentication.Cookies 的源码在 CookieAuthenticationHandler.cs 中将 TicketDataFormat 揪了出来:缓存
private async Task<AuthenticateResult> ReadCookieTicket() { var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName); //... var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding()); //... return AuthenticateResult.Success(ticket); }
TicketDataFormat的类型是ISecureDataFormat<AuthenticationTicket>接口,解密cookie就是调用这个接口的Unprotect方法:cookie
public interface ISecureDataFormat<TData> { string Protect(TData data); string Protect(TData data, string purpose); TData Unprotect(string protectedText); TData Unprotect(string protectedText, string purpose); }
并且TicketDataFormat是CookieAuthenticationOptions的一个属性,咱们能够直接修改这个属性值,使用本身的ISecureDataFormat接口实现(默认实现是SecureDataFormat),在Unprotect()方法的实现中读取protectedText参数值(这个应该就是接收到的cookie值)达到拦截目的,咱们试一下。mvc
定义一个 FormsAuthTicketDataFormat 类,实现 ISecureDataFormat<AuthenticationTicket> 接口:app
public class FormsAuthTicketDataFormat : ISecureDataFormat<AuthenticationTicket> { //... public AuthenticationTicket Unprotect(string protectedText, string purpose) { Console.WriteLine($"{nameof(Unprotect)}(\"{protectedText}\", \"{purpose}\")"); throw new NotImplementedException(); } }
在Startup.cs中应用FormsAuthTicketDataFormat:
var cookieOptions = new CookieAuthenticationOptions { //... TicketDataFormat = new FormsAuthTicketDataFormat() }; app.UseCookieAuthentication(cookieOptions);
经测试验证,接收到的的确是传统asp.net生成的cookie值。
既然在Unprotect()方法中已经能读取到cookie值,那紧接着就能够将它经过web api发送给传统asp.net解密,因而进入下一个问题。
问题三:传统asp.net如何在web api中从cookie中解密用户验证信息(FormsAuthenticationTicket)?
这个问题也很好解决,只需调用 FormsAuthentication.Decrypt() 方法进行解密,并将FormsAuthenticationTicket的Name, IssueDate, Expiration三个值返回给asp.net core。
public IHttpActionResult GetTicket(string cookie) { var formsAuthTicket = FormsAuthentication.Decrypt(cookie); return Ok(new { formsAuthTicket.Name, formsAuthTicket.IssueDate, formsAuthTicket.Expiration }); }
asp.net core中经过调用web api解密cookie并获得Name, IssueDate, Expiration这三个值,因而FormsAuthTicketDataFormat.Unprotect()的实现代码变成了这样:
public AuthenticationTicket Unprotect(string protectedText, string purpose) { var formsAuthTicket = GetFormsAuthTicket(protectedText); var name = formsAuthTicket.Name; DateTime issueDate = formsAuthTicket.IssueDate; DateTime expiration = formsAuthTicket.Expiration; throw new NotImplementedException(); }
接下来解决最后一个问题。
问题四:在asp.net core中如何将FormsAuthenticationTicket转换为自身的验证信息(AuthenticationTicket)?
因为Unprotect()方法返回参数的类型就是AuthenticationTicket,因此咱们不用换地方,继续在这个方法中折腾。如今咱们已经有了FormsAuthenticationTicket的三个值Name, IssueDate, Expiration,咱们须要基于它们建立有效的AuthenticationTicket。
AuthenticationTicket的构造函数有3个参数,第1个参数的类型是ClaimsPrincipal,与用户名相关联;第2个参数的类型是AuthenticationProperties,cookie的生成时间与过时时间就存储于其中,第3个参数authenticationScheme设置为对应的值(这里设置为空字符串),代码以下:
public AuthenticationTicket Unprotect(string protectedText, string purpose) { //Get FormsAuthenticationTicket from asp.net web api var formsAuthTicket = GetFormsAuthTicket(protectedText); var name = formsAuthTicket.Name; DateTime issueDate = formsAuthTicket.IssueDate; DateTime expiration = formsAuthTicket.Expiration; //Create AuthenticationTicket var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) }, "Basic"); var claimsPrincipal = new System.Security.Claims.ClaimsPrincipal(claimsIdentity); var authProperties = new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties { IssuedUtc = issueDate, ExpiresUtc = expiration }; var ticket = new AuthenticationTicket(claimsPrincipal, authProperties, _authenticationScheme); return ticket; }
解决这4个问题后就大功告成了!在aps.net core mvc controller中就能显示当前登陆用户名,好比下面的代码:
public IActionResult Index() { return Content(User.Identity.Name); }
完整相关实现代码以下:
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { var cookieOptions = new CookieAuthenticationOptions { AutomaticAuthenticate = true, AutomaticChallenge = true, CookieHttpOnly = true, CookieName = ".CnblogsCookie", CookieDomain = ".cnblogs.com", LoginPath = "/account/signin", TicketDataFormat = new FormsAuthTicketDataFormat("") }; app.UseCookieAuthentication(cookieOptions); //... }
FormAuthTicketDataFormat.cs
public class FormsAuthTicketDataFormat : ISecureDataFormat<AuthenticationTicket> { private string _authenticationScheme; public FormsAuthTicketDataFormat(string authenticationScheme) { _authenticationScheme = authenticationScheme; } public AuthenticationTicket Unprotect(string protectedText, string purpose) { //Get FormsAuthenticationTicket from asp.net web api var formsAuthTicket = GetFormsAuthTicket(protectedText); var name = formsAuthTicket.Name; DateTime issueDate = formsAuthTicket.IssueDate; DateTime expiration = formsAuthTicket.Expiration; //Create AuthenticationTicket var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) }, "Basic"); var claimsPrincipal = new System.Security.Claims.ClaimsPrincipal(claimsIdentity); var authProperties = new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties { IssuedUtc = issueDate, ExpiresUtc = expiration }; var ticket = new AuthenticationTicket(claimsPrincipal, authProperties, _authenticationScheme); return ticket; } public string Protect(AuthenticationTicket data) { throw new NotImplementedException(); } public string Protect(AuthenticationTicket data, string purpose) { throw new NotImplementedException(); } public AuthenticationTicket Unprotect(string protectedText) { throw new NotImplementedException(); } private FormsAuthTicketDto GetFormsAuthTicket(string cookie) { return new UserService().DecryptCookie(cookie).Result; } }
遗留问题:目前对[Authorize]标记不起做用。
更新:
遗留问题已解决,将
var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) });
改成
var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) }, "Basic");
也就是将authenticationType的值设为"Basic"。