ASP.NET Cookie是怎么生成的
ASP.NET Cookie是怎么生成的
可能有人知道Cookie
的生成由machineKey
有关,machineKey
用于决定Cookie
生成的算法和密钥,并如果使用多台服务器做负载均衡时,必须指定一致的machineKey
用于解密,那么这个过程到底是怎样的呢?
如果需要在.NET Core
中使用ASP.NET Cookie
,本文将提到的内容也将是一些必经之路。
抽丝剥茧,一步一步分析
首先用户通过AccountController->Login
进行登录:
//
// POST: /Account/Login
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
// ......省略其它代码
}
}
它调用了SignInManager
的PasswordSignInAsync
方法,该方法代码如下(有删减):
public virtual async Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
{
// ...省略其它代码
if (await UserManager.CheckPasswordAsync(user, password).WithCurrentCulture())
{
if (!await IsTwoFactorEnabled(user))
{
await UserManager.ResetAccessFailedCountAsync(user.Id).WithCurrentCulture();
}
return await SignInOrTwoFactor(user, isPersistent).WithCurrentCulture();
}
// ...省略其它代码
return SignInStatus.Failure;
}
想浏览原始代码,可参见官方的Github
链接:https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Owin/SignInManager.cs#L235-L276
可见它先需要验证密码,密码验证正确后,它调用了SignInOrTwoFactor
方法,该方法代码如下:
private async Task<SignInStatus> SignInOrTwoFactor(TUser user, bool isPersistent)
{
var id = Convert.ToString(user.Id);
if (await IsTwoFactorEnabled(user) && !await AuthenticationManager.TwoFactorBrowserRememberedAsync(id).WithCurrentCulture())
{
var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id));
AuthenticationManager.SignIn(identity);
return SignInStatus.RequiresVerification;
}
await SignInAsync(user, isPersistent, false).WithCurrentCulture();
return SignInStatus.Success;
}
该代码只是判断了是否需要做双重验证,在需要双重验证的情况下,它调用了AuthenticationManager
的SignIn
方法;否则调用SignInAsync
方法。SignInAsync
的源代码如下:
public virtual async Task SignInAsync(TUser user, bool isPersistent, bool rememberBrowser)
{
var userIdentity = await CreateUserIdentityAsync(user).WithCurrentCulture();
// Clear any partial cookies from external or two factor partial sign ins
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie);
if (rememberBrowser)
{
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity);
}
else
{
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity);
}
}
可见,最终所有的代码都是调用了AuthenticationManager.SignIn
方法,所以该方法是创建Cookie
的关键。
AuthenticationManager
的实现定义在Microsoft.Owin
中,因此无法在ASP.NET Identity
中找到其源代码,因此我们打开Microsoft.Owin
的源代码继续跟踪(有删减):
public void SignIn(AuthenticationProperties properties, params ClaimsIdentity[] identities)
{
AuthenticationResponseRevoke priorRevoke = AuthenticationResponseRevoke;
if (priorRevoke != null)
{
// ...省略不相关代码
AuthenticationResponseRevoke = new AuthenticationResponseRevoke(filteredSignOuts);
}
AuthenticationResponseGrant priorGrant = AuthenticationResponseGrant;
if (priorGrant == null)
{
AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identities), properties);
}
else
{
// ...省略不相关代码
AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(mergedIdentities), priorGrant.Properties);
}
}
AuthenticationManager
的Github
链接如下:https://github.com/aspnet/AspNetKatana/blob/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/Microsoft.Owin/Security/AuthenticationManager.cs
可见它用到了AuthenticationResponseGrant
,继续跟踪可以看到它实际是一个属性:
public AuthenticationResponseGrant AuthenticationResponseGrant
{
// 省略get
set
{
if (value == null)
{
SignInEntry = null;
}
else
{
SignInEntry = Tuple.Create((IPrincipal)value.Principal, value.Properties.Dictionary);
}
}
}
发现它其实是设置了SignInEntry
,继续追踪:
public Tuple<IPrincipal, IDictionary<string, string>> SignInEntry
{
get { return _context.Get<Tuple<IPrincipal, IDictionary<string, string>>>(OwinConstants.Security.SignIn); }
set { _context.Set(OwinConstants.Security.SignIn, value); }
}
其中,_context
的类型为IOwinContext
,OwinConstants.Security.SignIn
的常量值为"security.SignIn"
。
跟踪完毕……
啥?跟踪这么久,居然跟丢啦!?
当然没有!但接下来就需要一定的技巧了。
原来,ASP.NET
是一种中间件(Middleware
)模型,在这个例子中,它会先处理MVC
中间件,该中间件处理流程到设置AuthenticationResponseGrant
/SignInEntry
为止。但接下来会继续执行CookieAuthentication
中间件,该中间件的核心代码在aspnet/AspNetKatana
仓库中可以看到,关键类是CookieAuthenticationHandler
,核心代码如下:
protected override async Task ApplyResponseGrantAsync()
{
AuthenticationResponseGrant signin = Helper.LookupSignIn(Options.AuthenticationType);
// ... 省略部分代码
if (shouldSignin)
{
var signInContext = new CookieResponseSignInContext(
Context,
Options,
Options.AuthenticationType,
signin.Identity,
signin.Properties,
cookieOptions);
// ... 省略部分代码
model = new AuthenticationTicket(signInContext.Identity, signInContext.Properties);
// ... 省略部分代码
string cookieValue = Options.TicketDataFormat.Protect(model);
Options.CookieManager.AppendResponseCookie(
Context,
Options.CookieName,
cookieValue,
signInContext.CookieOptions);
}
// ... 又省略部分代码
}
这个原始函数有超过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
(有删减):
public AuthenticationResponseGrant LookupSignIn(string authenticationType)
{
// ...
AuthenticationResponseGrant grant = _context.Authentication.AuthenticationResponseGrant;
// ...
foreach (var claimsIdentity in grant.Principal.Identities)
{
if (string.Equals(authenticationType, claimsIdentity.AuthenticationType, StringComparison.Ordinal))
{
return new AuthenticationResponseGrant(claimsIdentity, grant.Properties ?? new AuthenticationProperties());
}
}
return null;
}
如此一来,柳暗花明又一村,所有的线索就立即又明朗了。
Cookie的生成
从AuthenticationTicket
变成Cookie
字节串,最关键的一步在这里:
string cookieValue = Options.TicketDataFormat.Protect(model);
在接下来的代码中,只提到使用CookieManager
将该Cookie
字节串添加到Http
响应中,翻阅CookieManager
可以看到如下代码:
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
IHeaderDictionary responseHeaders = context.Response.Headers;
// 省去“1万”行计算chunk和处理细节的流程
responseHeaders.AppendValues(Constants.Headers.SetCookie, chunks);
}
有兴趣的朋友可以访问Github
看原始版本的代码:https://github.com/aspnet/AspNetKatana/blob/0fc4611e8b04b73f4e6bd68263e3f90e1adfa447/src/Microsoft.Owin/Infrastructure/ChunkingCookieManager.cs#L125-L215
可见这个实现比较……简单,就是往Response.Headers
中加了个头,重点只要看TicketDataFormat.Protect
方法即可。
逐渐明朗
该方法源代码如下:
public string Protect(TData data)
{
byte[] userData = _serializer.Serialize(data);
byte[] protectedData = _protector.Protect(userData);
string protectedText = _encoder.Encode(protectedData);
return protectedText;
}
可见它依赖于_serializer
、_protector
、_encoder
三个类,其中,_serializer
的关键代码如下:
public virtual byte[] Serialize(AuthenticationTicket model)
{
using (var memory = new MemoryStream())
{
using (var compression = new GZipStream(memory, CompressionLevel.Optimal))
{
using (var writer = new BinaryWriter(compression))
{
Write(writer, model);
}
}
return memory.ToArray();
}
}
其本质是进行了一次二进制序列化,并紧接着进行了gzip
压缩,确保Cookie
大小不要失去控制(因为.NET
的二进制序列化结果较大,并且微软喜欢搞xml
,更大
ASP.NET Cookie是怎么生成的的更多相关文章
- ASP.NET Cookie 概述【转】
来源:http://msdn.microsoft.com/zh-cn/library/ms178194(VS.80).aspx ASP.NET Cookie 概述 Cookie 提供了一种在 Web ...
- asp.net下调用Matlab生成动态链接库
对于这次论文项目,最后在写一篇关于工程的博客,那就是在asp.net下调用matlab生成的dll动态链接库.至今关于matlab,c/c++(opencv),c#(asp.net)我总共写了4篇配置 ...
- 【转】asp.net Cookie值中文乱码问题解决方法
来源:脚本之家.百度空间.网易博客 http://www.jb51.net/article/34055.htm http://hi.baidu.com/honfei http://tianminqia ...
- ASP.NET WebAPI使用Swagger生成测试文档
ASP.NET WebAPI使用Swagger生成测试文档 SwaggerUI是一个简单的Restful API测试和文档工具.简单.漂亮.易用(官方demo).通过读取JSON配置显示API .项目 ...
- How to secure the ASP.NET_SessionId cookie? 设置ASP.NET_SessionId Secure=true
How to secure the ASP.NET_SessionId cookie? To add the ; secure suffix to the Set-Cookie http header ...
- 由Asp.Net客户端控件生成的服务器端控件
由Asp.Net客户端控件生成的服务器端控件(也就是给客户端控件添加 runnat="server"),这样的控件既能在js中通过id命.类名获取到,也能在服务器端根据id获取到
- ASP组件AspJpeg(加水印)生成缩略图等使用方法
ASP组件AspJpeg(加水印)生成缩略图等使用方法 作者: 字体:[增加 减小] 类型:转载 时间:2012-12-17我要评论 ASPJPEG是一款功能相当强大的图象处理组件,用它可以轻松地做出 ...
- asp.net core web api 生成 swagger 文档
asp.net core web api 生成 swagger 文档 Intro 在前后端分离的开发模式下,文档就显得比较重要,哪个接口要传哪些参数,如果一两个接口还好,口头上直接沟通好就可以了,如果 ...
- ASP.NET Cookie和Session
Cookie和Session C#在服务器,JS在客户端 客户端验证不能代替服务端验证 Http HTTP属于应用层,HTTP 协议一共有五大特点:1.支持客户/服务器模式;2.简单快速;3.灵活;4 ...
随机推荐
- element ui 批量删除
<el-table :data="tableData" stripe border style="width: 100%" @selection-chan ...
- Nginx与PHP交互过程 + Nginx与PHP通信的两种方式
一.Nginx与PHP交互过程的7步走(用户对动态PHP网页访问过程) step1:用户将http请求发送给nginx服务器(用户和nginx服务器进行三次握手进行TCP连接) step2:nginx ...
- Channel 9视频整理【2】
JadeChang https://channel9.msdn.com/Niners/JadeChang 繁体中文视频 2016 Nano Server / Docker / Containers 打 ...
- linux c函数参考手册
一.字符测试 isalnum(测试字符是否为英文字母或数字) isalpha(测试字符是否为英文字母) isascii(测试字符是否为ascii码字符) isblank(测试字符是否为空格字符) is ...
- looping through multiple lists
map: 最大长度输出: zip: 最短输出: third: 有序排列: a = ['a1', 'a2', 'a3'] b = ['b1', 'b2'] print "Map:" ...
- 用Tasker实现收到Android手机短信自动转发到邮箱
发送短信到邮箱的原理与 <用Tasker实现收到Android手机短信自动转发到邮箱>有些类似. 发送短信到邮箱是利用Ifttt这个服务将短信转发到邮箱中.Ifttt服务的可扩展性很强, ...
- 前端-jQuery介绍
目录 jQuery介绍 jQuery的优势 jQuery内容: jQuery版本 jQuery对象 jQuery基础语法 查找标签 基本选择器 层级选择器: 基本筛选器: 属性选择器: 表单筛选器: ...
- Codeforces Round #612 (Div. 2)
https://codeforces.com/contest/1287/ A - Angry Students 题意:求A后面的P最长连续有几个? 题解:? int n; char s[200005] ...
- CS系列(一):Cobalt Strike初体验
0X01 环境准备 Cobalt Strike安装包(链接:https://pan.baidu.com/s/1wCHJcJrF6H0wUCdetz3Xjg 提取码:rjuj) Cobalt Strik ...
- java 多线程 快速入门
------------恢复内容开始------------ java 多线程 快速入门 1. 进程和线程 什么是进程? 进程是正在运行的程序它是线程的集合 进程中一定有一个主线程 一个操作系统可以有 ...