在解決了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"。