系统城装机大师 - 固镇县祥瑞电脑科技销售部宣传站!

当前位置:首页 > 网络编程 > ASP.NET > 详细页面

ASP.NET Cookie是怎么生成的(推荐)

时间:2020-02-03来源:系统城作者:电脑系统城

可能有人知道Cookie的生成由machineKey有关,machineKey用于决定Cookie生成的算法和密钥,并如果使用多台服务器做负载均衡时,必须指定一致的machineKey用于解密,那么这个过程到底是怎样的呢?

如果需要在.NET Core中使用ASP.NET Cookie,本文将提到的内容也将是一些必经之路。

抽丝剥茧,一步一步分析
首先用户通过AccountController->Login进行登录:


 
  1. //
  2. // POST: /Account/Login
  3. public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
  4. {
  5. if (!ModelState.IsValid)
  6. {
  7. return View(model);
  8. }
  9.  
  10. var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password,model.RememberMe, shouldLockout: false);
  11. switch (result)
  12. {
  13. case SignInStatus.Success:
  14. return RedirectToLocal(returnUrl);
  15. // ......省略其它代码
  16. }
  17. }

它调用了SignInManager的PasswordSignInAsync方法,该方法代码如下(有删减):


 
  1. public virtual async Task<SignInStatus> PasswordSignInAsync(string userName, stringpassword, bool isPersistent, bool shouldLockout)
  2. {
  3. // ...省略其它代码
  4. if (await UserManager.CheckPasswordAsync(user, password).WithCurrentCulture())
  5. {
  6. if (!await IsTwoFactorEnabled(user))
  7. {
  8. await UserManager.ResetAccessFailedCountAsync(user.Id).WithCurrentCulture();
  9. }
  10. return await SignInOrTwoFactor(user, isPersistent).WithCurrentCulture();
  11. }
  12. // ...省略其它代码
  13. return SignInStatus.Failure;
  14. }

想浏览原始代码,可参见官方的Github链接:

https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Owin/SignInManager.cs#L235-L276

可见它先需要验证密码,密码验证正确后,它调用了SignInOrTwoFactor方法,该方法代码如下:


 
  1. private async Task<SignInStatus> SignInOrTwoFactor(TUser user, bool isPersistent)
  2. {
  3. var id = Convert.ToString(user.Id);
  4. if (await IsTwoFactorEnabled(user) && !await AuthenticationManager.TwoFactorBrowserRememberedAsync(id).WithCurrentCulture())
  5. {
  6. var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
  7. identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id));
  8. AuthenticationManager.SignIn(identity);
  9. return SignInStatus.RequiresVerification;
  10. }
  11. await SignInAsync(user, isPersistent, false).WithCurrentCulture();
  12. return SignInStatus.Success;
  13. }

该代码只是判断了是否需要做双重验证,在需要双重验证的情况下,它调用了AuthenticationManager的SignIn方法;否则调用SignInAsync方法。SignInAsync的源代码如下:


 
  1. public virtual async Task SignInAsync(TUser user, bool isPersistent, bool rememberBrowser)
  2. {
  3. var userIdentity = await CreateUserIdentityAsync(user).WithCurrentCulture();
  4. // Clear any partial cookies from external or two factor partial sign ins
  5. AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie,DefaultAuthenticationTypes.TwoFactorCookie);
  6. if (rememberBrowser)
  7. {
  8. var rememberBrowserIdentity =AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
  9. AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent },userIdentity, rememberBrowserIdentity);
  10. }
  11. else
  12. {
  13. AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent },userIdentity);
  14. }
  15. }

可见,最终所有的代码都是调用了AuthenticationManager.SignIn方法,所以该方法是创建Cookie的关键。

AuthenticationManager的实现定义在Microsoft.Owin中,因此无法在ASP.NET Identity中找到其源代码,因此我们打开Microsoft.Owin的源代码继续跟踪(有删减):


 
  1. public void SignIn(AuthenticationProperties properties, params ClaimsIdentity[] identities)
  2. {
  3. AuthenticationResponseRevoke priorRevoke = AuthenticationResponseRevoke;
  4. if (priorRevoke != null)
  5. {
  6. // ...省略不相关代码
  7. AuthenticationResponseRevoke = new AuthenticationResponseRevoke(filteredSignOuts);
  8. }
  9.  
  10. AuthenticationResponseGrant priorGrant = AuthenticationResponseGrant;
  11. if (priorGrant == null)
  12. {
  13. AuthenticationResponseGrant = new AuthenticationResponseGrant(newClaimsPrincipal(identities), properties);
  14. }
  15. else
  16. {
  17. // ...省略不相关代码
  18.  
  19. AuthenticationResponseGrant = new AuthenticationResponseGrant(newClaimsPrincipal(mergedIdentities), priorGrant.Properties);
  20. }
  21. }

AuthenticationManager的Github链接如下:https://github.com/aspnet/AspNetKatana/blob/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/Microsoft.Owin/Security/AuthenticationManager.cs

可见它用到了AuthenticationResponseGrant,继续跟踪可以看到它实际是一个属性:


 
  1. public AuthenticationResponseGrant AuthenticationResponseGrant
  2. {
  3. // 省略get
  4. set
  5. {
  6. if (value == null)
  7. {
  8. SignInEntry = null;
  9. }
  10. else
  11. {
  12. SignInEntry = Tuple.Create((IPrincipal)value.Principal, value.Properties.Dictionary);
  13. }
  14. }
  15. }

发现它其实是设置了SignInEntry,继续追踪:


 
  1. public Tuple<IPrincipal, IDictionary<string, string>> SignInEntry
  2. {
  3. get { return _context.Get<Tuple<IPrincipal, IDictionary<string, string>>>(OwinConstants.Security.SignIn); }
  4. set { _context.Set(OwinConstants.Security.SignIn, value); }
  5. }

其中,_context的类型为IOwinContext,OwinConstants.Security.SignIn的常量值为"security.SignIn"。

跟踪完毕……

啥?跟踪这么久,居然跟丢啦!?
当然没有!但接下来就需要一定的技巧了。

原来,ASP.NET是一种中间件(Middleware)模型,在这个例子中,它会先处理MVC中间件,该中间件处理流程到设置AuthenticationResponseGrant/SignInEntry为止。但接下来会继续执行CookieAuthentication中间件,该中间件的核心代码在aspnet/AspNetKatana仓库中可以看到,关键类是CookieAuthenticationHandler,核心代码如下:

 


 
  1. protected override async Task ApplyResponseGrantAsync()
  2. {
  3. AuthenticationResponseGrant signin = Helper.LookupSignIn(Options.AuthenticationType);
  4. // ... 省略部分代码
  5.  
  6. if (shouldSignin)
  7. {
  8. var signInContext = new CookieResponseSignInContext(
  9. Context,
  10. Options,
  11. Options.AuthenticationType,
  12. signin.Identity,
  13. signin.Properties,
  14. cookieOptions);
  15.  
  16. // ... 省略部分代码
  17.  
  18. model = new AuthenticationTicket(signInContext.Identity, signInContext.Properties);
  19. // ... 省略部分代码
  20.  
  21. string cookieValue = Options.TicketDataFormat.Protect(model);
  22.  
  23. Options.CookieManager.AppendResponseCookie(
  24. Context,
  25. Options.CookieName,
  26. cookieValue,
  27. signInContext.CookieOptions);
  28. }
  29. // ... 又省略部分代码
  30. }

这个原始函数有超过200行代码,这里我省略了较多,但保留了关键、核心部分,想查阅原始代码可以移步Github链接:https://github.com/aspnet/AspNetKatana/blob/0fc4611e8b04b73f4e6bd68263e3f90e1adfa447/src/Microsoft.Owin.Security.Cookies/CookieAuthenticationHandler.cs#L130-L313

这里挑几点最重要的讲。

与MVC建立关系

建立关系的核心代码就是第一行,它从上文中提到的位置取回了AuthenticationResponseGrant,该Grant保存了Claims、AuthenticationTicket等Cookie重要组成部分:

AuthenticationResponseGrant signin = Helper.LookupSignIn(Options.AuthenticationType);
继续查阅LookupSignIn源代码,可看到,它就是从上文中的AuthenticationManager中取回了AuthenticationResponseGrant(有删减):


 
  1. public AuthenticationResponseGrant LookupSignIn(string authenticationType)
  2. {
  3. // ...
  4. AuthenticationResponseGrant grant =_context.Authentication.AuthenticationResponseGrant;
  5. // ...
  6.  
  7. foreach (var claimsIdentity in grant.Principal.Identities)
  8. {
  9. if (string.Equals(authenticationType, claimsIdentity.AuthenticationType,StringComparison.Ordinal))
  10. {
  11. return new AuthenticationResponseGrant(claimsIdentity, grant.Properties ?? newAuthenticationProperties());
  12. }
  13. }
  14.  
  15. return null;
  16. }

如此一来,柳暗花明又一村,所有的线索就立即又明朗了。

Cookie的生成

从AuthenticationTicket变成Cookie字节串,最关键的一步在这里:

string cookieValue = Options.TicketDataFormat.Protect(model);
在接下来的代码中,只提到使用CookieManager将该Cookie字节串添加到Http响应中,翻阅CookieManager可以看到如下代码:


 
  1. public void AppendResponseCookie(IOwinContext context, string key, string value,CookieOptions options)
  2. {
  3. if (context == null)
  4. {
  5. throw new ArgumentNullException("context");
  6. }
  7. if (options == null)
  8. {
  9. throw new ArgumentNullException("options");
  10. }
  11.  
  12. IHeaderDictionary responseHeaders = context.Response.Headers;
  13. // 省去“1万”行计算chunk和处理细节的流程
  14. responseHeaders.AppendValues(Constants.Headers.SetCookie, chunks);
  15. }

有兴趣的朋友可以访问Github看原始版本的代码:https://github.com/aspnet/AspNetKatana/blob/0fc4611e8b04b73f4e6bd68263e3f90e1adfa447/src/Microsoft.Owin/Infrastructure/ChunkingCookieManager.cs#L125-L215

可见这个实现比较……简单,就是往Response.Headers中加了个头,重点只要看TicketDataFormat.Protect方法即可。

逐渐明朗

该方法源代码如下:


 
  1. public string Protect(TData data)
  2. {
  3. byte[] userData = _serializer.Serialize(data);
  4. byte[] protectedData = _protector.Protect(userData);
  5. string protectedText = _encoder.Encode(protectedData);
  6. return protectedText;
  7. }

可见它依赖于_serializer、_protector、_encoder三个类,其中,_serializer的关键代码如下:


 
  1. public virtual byte[] Serialize(AuthenticationTicket model)
  2. {
  3. using (var memory = new MemoryStream())
  4. {
  5. using (var compression = new GZipStream(memory, CompressionLevel.Optimal))
  6. {
  7. using (var writer = new BinaryWriter(compression))
  8. {
  9. Write(writer, model);
  10. }
  11. }
  12. return memory.ToArray();
  13. }
  14. }

其本质是进行了一次二进制序列化,并紧接着进行了gzip压缩,确保Cookie大小不要失去控制(因为.NET的二进制序列化结果较大,并且微软喜欢搞xml,更大

分享到:

相关信息

系统教程栏目

栏目热门教程

人气教程排行

站长推荐

热门系统下载