微软在早期 .NET Framework 时代,针对 ASP.NET 的用户登录身份认证,提供了 Forms 认证实现方案。后来在推出 ASP.NET Core 之后,采用 Claim 认证替代了 Forms 认证,两者本质上都是基于 Cookie 加解密的认证方式,学习和使用起来非常简单,比较适合在小型项目中使用,主要是方便。
假设我们现在已经创建好了一个基于 .NET5 模型-视图-控制器 的 ASP.NET Core Web 应用 (这里就不介绍创建过程了),下面我就基于 .NET5 网站,介绍一下 Claim 认证的具体实现方式:
1 修改 appsettings.json 配置文件,增加有关 Cookie 的相关配置
在实际开发场景中,我们一般将 Cookie 相关信息进行配置,所以我们增加以下配置信息:
{
// 这里采用了两级嵌套的 json 配置方式
"web": {
// 用户未登录时,自动跳转的目标路由地址
"loginUrl": "/Home/Login",
// Cookie 的名字
"cookieName": "UserAuth",
// Cookie 保存的路径,这里保存在根路径
"cookiePath": "/",
// Cookie 的所属的域名
"cookieDomain": ".demo.com"
}
}
其实上面的 json 配置中, Cookie 的保存路径,所属域名,都可以配置为空,这样不影响 Claim 认证的实现。
但是有一种情况例外,那就是万恶的 IE 浏览器。对于 IE 浏览器来说,上面的 json 配置中,必须配置好 Cookie 的保存路径和域名,否则无法生成 Cookie,无法实现 Claim 认证。
如果你实施的项目不考虑 IE 浏览器的话,那就很幸运了。但是目前国内的很多银行、政府、国企,未来很长一段时间仍然还在使用 IE 浏览器,所以有时候还得被迫考虑 IE 浏览器。
2 修改 Startup.cs 文件,增加 Claim 认证支持
需要修改两个地方 ConfigureServices 方法 和 Configure方法
在 ConfigureServices 方法 中添加 Cookie 认证方式,在 Configure方法 中使用认证和授权。
注意:Configure 方法每行代码的顺序位置很重要,新增加的两行代码(app.UseAuthentication 和 app.UseAuthorization 必须在 app.UseRouting 之后,app.UseEndpoints 之前)
public void ConfigureServices(IServiceCollection services, IConfiguration Config)
{
services.AddControllersWithViews();
//添加 Cookie 认证方式
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
//从配置节中读取配置的 Cookie 信息
options.LoginPath = Config["web:loginUrl"];
options.Cookie.Name = Config["web:cookieName"];
options.Cookie.Path = Config["web:cookiePath"];
options.Cookie.Domain = Config["web:cookieDomain"];
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStaticFiles();
app.UseRouting();
//使用认证和授权
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Web}/{action=Index}");
});
}
3 在相关 Controller 中的 Action 方法添加 Claim 认证的代码
假设我们在登录页面,输入了用户名密码,提交到后台的 Home/Login 中,该 Action 已经在数据库中验证了用户名和密码的正确性,接下来就要生成 Claim 认证的 Cookie,代码如下:
public IActionResult Login(string name, string pwd)
{
//......此处省略了去数据库验证用户名和密码,以及获取用户的权限的相关代码
//可以添加额外的信息存储在cookie中
//这里把从数据库中获取的【用户角色】和【权限码】进行存储
var claims = new[] { new Claim("Power", power), new Claim("Role", role) };
//这里存储【用户名】
//这样后面就可以通过 User.Identity.Name 获取到用户名了
GenericIdentity gi = new GenericIdentity(name);
ClaimsIdentity ci = new ClaimsIdentity(gi, claims,
CookieAuthenticationDefaults.AuthenticationScheme, "", "");
ClaimsPrincipal cp = new ClaimsPrincipal(ci);
Task.Run(async () =>
{
//生成 Cookie 并保存到硬盘中,指定 Cookie 的过期时间
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,cp, new AuthenticationProperties()
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60),
AllowRefresh = true
});
}).Wait();
return Content("login success");
}
前端通过 Ajax 调用后端登录方法后,如果成功,然后跳转到网站主页即可。
4 控制 Controller 和 Action 的访问
我们可以通过在 Controller 上或 Action 上增加 [Authorize] 标记 或 [AllowAnonymous] 标记,来控制用户对具体 Controller 或 Action 的访问,这跟以前的 Forms 认证的控制方式一模一样。
[Authorize] 标记:
表示必须拥有 Claim 的 Cookie 才能访问,否则就会跳转到指定的页面,我们一般指定跳转到登录页面。可以放到 Controller 或 Action 上。如果该标记添加到 Controller 上,则该 Controller 下的所有 Action 都会验证是否拥有 Claim 的 Cookie。
[AllowAnonymous] 标记:
表示允许匿名用户访问,没有登录也可以访问,也就是不需要 Claim 的 Cookie 就可以访问。可以放到 Controller 或 Action 上。通常的使用场景是:在 Controller 上放了 [Authorize] 之后,在该 Controller 下具体的一小部分 Action 上放 [AllowAnonymous] 标记。这样就保证 Controller 下只有一小部分 Action 允许未登录用户访问,其它的 Action 必须用户登录后才能访问。
5 获取 Claim 的 Cookie 中的信息以及退出登录
在上面第 3 步中,我们通过 GenericIdentity gi = new GenericIdentity(name) 这行代码存储登录的用户名,因此我们可以通过 User.Identity.Name 获取到登录的用户名。
下面列出获取 Cookie 中存储的其它信息,比如上面存储的 Power 和 Role
[Authorize]
public IActionResult Display()
{
StringBuilder sb = new StringBuilder();
// 遍历获取所有的存储信息
foreach (var cl in HttpContext.User.Claims)
{
sb.AppendLine($"{cl.Type}|{cl.Value}");
}
// 通过便捷的方式获取登录的用户名
sb.AppendLine(User.Identity.Name);
// 想要获取到具体的一项存储信息,比如仅仅想获取权限码和角色
sb.AppendLine(User.FindFirstValue("Power"));
sb.AppendLine(User.FindFirstValue("Role"));
return Content(sb.ToString());
}
如果想要用户退出登录,销毁 Cookie 的话,可以采用以下代码:
public IActionResult LogOut()
{
if (User.Identity.IsAuthenticated)
{
Task.Run(async () => { await HttpContext.SignOutAsync(); }).Wait();
}
//销毁 Claim 的 Cookie 后,可以跳转到登录页面
return RedirectToAction("Index", "Web");
}
6 优缺点闲谈
到此为止,ASP.NET Core 网站使用 Claim 认证,实现用户登录,访问时进行身份验证方案,已经介绍完了。如果你想要快速搭建轻量级网站应用的话,使用 Claim 认证方式是一种非常方便快速的方案。
但是不建议在大中型项目中使用,还是采用主流的 token + redis 的方案实现用户登录认证比较好,原因如下:
- 后续项目肯定会涉及到各个系统之间的统一认证对接,以及与第三方的单点登录对接,这种情景使用 token + redis 的方案比较灵活简单,采用 Cookie 的实现方式比较麻烦。
- 对于用户请求负载均衡分发的场景,token + redis 的方案是非常好的方案,因为其天然保证所有请求都是无状态的,不需要在负载均衡服务器上配置会话保持,这样负载均衡服务器就可以根据每台应用服务器的负载状况,随意使用任何负载请求分发策略。
- 对于负载均衡来说, Claim 认证方案就不够灵活了,因为其 Cookie 只能在具体的服务器上进行加解密,从而识别登录用户。如果新的请求被负载均衡服务器分发到另一台应用服务器的话,那么就无法解密 Cookie 从而导致需要频繁重新登录,无法使用网站。只有在负载均衡服务器上配置会话保持,从而实现在一定的时间内将来自同一 ip 的请求分发到固定的应用服务器上,才能解决问题。(具体在各个不同的服务器之间,如何实现 Claim 的 Cookie 采用统一的加解密算法,我个人没有研究过。其实也没必要研究了,因为我们大部分情况下都会使用目前比较流行的 token + redis 方案,能够轻松解决任何问题)