.NET跨平台之旅:ASP.NET Core从传统ASP.NET的Cookie中读取用户登录信息

时间:2022-09-07 13:05:13

在解决了asp.net core中访问memcached缓存的问题后,我们开始大踏步地向.net core进军——将更多站点向asp.net core迁移,在迁移涉及获取用户登录信息的站点时,我们遇到了一个问题——如何在asp.net core与传统asp.net之间共享保存用户登录信息的cookie?

对于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站点的代码,有些奢侈,不到万不得已,我们不想采用,我们要另辟蹊径。

先简化一下问题,根据我们向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)?我们来逐一解决这些问题。

问题一:在asp.net core中如何接收传统asp.net的cookie?

这个问题很容易解决。只需在Startup.cs中将CookieAuthenticationOptions的CookieName与CookieDomain设置为与传统asp.net一样。

var cookieOptions = new CookieAuthenticationOptions
{
CookieName = ".CnblogsCookie",
CookieDomain = ".cnblogs.com",
};
app.UseCookieAuthentication(cookieOptions);

问题二:如何拦截asp.net core对cookie的解密操作?

这个问题比较棘手。我们是通过阅读 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方法:

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值)达到拦截目的,我们试一下。

定义一个 FormsAuthTicketDataFormat 类,实现 ISecureDataFormat<AuthenticationTicket> 接口:

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"。

.NET跨平台之旅:ASP.NET Core从传统ASP.NET的Cookie中读取用户登录信息的更多相关文章

  1. Asp&period;Net Core 项目实战之权限管理系统(5) 用户登录

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  2. ASP&period;NET Core&colon; 全新的ASP&period;NET &excl;

    背景 最新版本的 ASP.NET 叫做 ASP.NET Core (也被称为 ASP.NET 5)   它颠覆了过去的 ASP.NET. 什么是 ASP.NET Core? ASP.NET Core ...

  3. ASP&period;NET Core 基础教程 - ASP&period;NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 基础教程 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 是对 ASP.NET 有重大意义的一次重新设计.本章节我们将介绍 A ...

  4. ASP&period;NET Core Identity 配置 - ASP&period;NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 配置 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 配置 上一章节我们简单介绍了下 Id ...

  5. ASP&period;NET Core Identity 框架 - ASP&period;NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 框架 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 框架 前面我们使用了 N 多个章节, ...

  6. ASP&period;NET Core 数据库上下文 - ASP&period;NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 数据库上下文 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 数据库上下文 上一章节中我们了解了 Entity Framewo ...

  7. ASP&period;NET Core 动作结果 - ASP&period;NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 动作结果 前面的章节中,我们一直使用简单的 C# 类作为控制器. 虽 ...

  8. ASP&period;NET Core 属性路由 - ASP&period;NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 属性路由 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 属性路由 经过前面章节的学习,想必你已经对 ASP.NET Core ...

  9. ASP&period;NET Core MVC 设计模式 - ASP&period;NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core MVC 设计模式 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core MVC 设计模式 上一章节中,我们提到 ASP.NET Co ...

随机推荐

  1. 【算法】C语言实现数组的动态分配

    C语言实现数组的动态分配 作者:白宁超 2016年10月27日20:13:13 摘要:数据结构和算法对于编程的意义不言而喻,具有指导意义的.无论从事算法优化方向研究,还是大数据处理,亦或者网站开发AP ...

  2. Javascript设计模式学习二(单例)

    定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点 普通的单例模式: 使用一个变量来标记当前是否已经为某个类创建过对象,如果是的话,在下一次获取该类的实例时,直接返回之前创建的对象.比如:使用 ...

  3. 深入理解Javascript中构造函数和原型对象的区别

    在 Javascript中prototype属性的详解 这篇文章中,详细介绍了构造函数的缺点以及原型(prototype),原型链(prototype chain),构造函数(constructor) ...

  4. 文件编码、charset、sublime编辑器支持GBK等问题

    问题一:如何让sublime3支持GBK 首先打开package control ,然后键入install package进入,搜索ConvertToUTF8安装成功后 打开要查看的GBK文件,点击菜 ...

  5. 深入剖析Java中的自动装箱和拆箱过程

    深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...

  6. FFT快速傅立叶

    Description 给出两个n位10进制整数x和y,你需要计算x*y. Input 第一行一个正整数n.第二行描述一个位数为n的正整数x.第三行描述一个位数为n的正整数y. Output 输出一行 ...

  7. JavaBean-- 保存 范围

    1. page:当前页 2. request:一次服务器跳转范围中 3. session:一次用户操作范围,重新打开浏览器失效 4. application:整个服务器保存,服务器关闭才失效 定义一个 ...

  8. 页面刷新vuex数据消失问题解决方案

    VBox持续进行中,哀家苦啊,有没有谁给个star. vuex是vue用于数据存储的,和redux充当同样的角色. 最近在VBox开发的时候遇到的问题,页面刷新或者关闭浏览器再次打开的时候数据归零.这 ...

  9. web API简介&lpar;三&rpar;:客户端储存之Web Storage API

    概述 前篇:web API简介(二):客户端储存之document.cookie API 客户端储存从某一方面来说和动态网站差不多.动态网站是用服务端来储存数据,而客户端储存是用客户端来储存数据. W ...

  10. django crontab 定时任务

    分 时 日 月 周 命令(最好用绝对路径)比如: * * * * * rm -fr /mnt/* //每分钟执行一次对/mnt目录下文件的删除*/2 * * * * rm -fr /mnt/* //每 ...