Overview

身份认证是网站最基本的功能,最近因为业务部门的一个需求,需要对一个已经存在很久的小工具网站进行改造,因为在逐步的将一些离散的系统迁移至 .NET Core,所以趁这个机会将这个老的 .NET Framework 4.0 的项目进行升级

老的项目是一个 MVC 的项目并且有外网访问的需求,大部门的微服务平台因为和内部的业务执行比较密切,介于资安要求与外网进行了隔离,因此本次升级就不会迁移到该平台上进行前后端分离改造

使用频次不高,不存在高并发,实现周期短,所以就没有必要为了用某些组件而用,因此这里还是选择沿用 MVC 框架,对于网站的身份认证则采用单体应用最常见的 Cookie 认证来实现,本篇文章则是如何实现的一个基础的教程,仅供参考

Step by Step

在涉及到系统权限管理的相关内容时,必定会提到两个长的很像的单词,authentication(认证) 和 authorization(授权)

  • authentication:用一些数据来证明你就是你,登录系统、指纹、面部解锁就是一种认证的过程
  • authorization:授予一些用户去访问一些特殊资源或功能的过程,系统包含管理员和普通用户两种角色,只有管理员才可以执行某些操作,赋予管理员角色某些操作的过程就是授权

只有认证和授权一起配合,才可以完成对于整个系统的权限管控

2.1、前期准备

假定现在已经存在了一个 ASP.NET Core MVC 应用,这里以 VS 创建的默认项目为例,对于一个 MVC or Web API 应用,要求用户必须登录之后才能进行访问,最简单的方式,在需要认证的 Controller 或 Action 上添加 Authorize 特性,然后在 Startup.Configure 方法中通过 UseAuthorization 添加中间件即可

[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
}

当然,当系统只包含一个两个 Controller 时还好,当系统比较复杂的时候,再一个个的添加 Authorize 特性就比较麻烦了,因此这里我们可以通过在 Startup.ConfigureServices 中添加全局的 AuthorizeFilter 过滤器,实现对于全局的认证管控

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddMvcOptions(options => { options.Filters.Add(new AuthorizeFilter()); }); }
}

此时,对于一些不需要进行认证就可以访问的页面,只需要添加 AllowAnonymous 特性即可

public class AuthenticationController : Controller
{
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
}

2.2、配置认证策略

当然,如果只是这样修改的话,其实是有问题的,可以看到,当添加上全局过滤器后,系统已经无法正常的进行访问

对于 authorization(授权) 来说,它其实是在 authentication(认证)通过之后才会进行的操作,也就是说这里我们缺少了对于系统认证的配置,依据报错信息的提示,我们首先需要通过使用 AddAuthentication 方法来定义系统的认证策略

AddAuthentication 方法位于 Microsoft.AspNetCore.Authentication 类库中,通过在 Nuget 中搜索就可以发现,.NET Core 已经基于业界通用的规范实现了多个认证策略

因为这里使用的 Cookie 认证已经包含在默认的项目模板中了,所以就不需要再引用了

基于 .NET Core 标准的服务使用流程,首先,我们需要在 Startup.ConfigureServices 方法来中通过 AddAuthentication 来定义整个系统所使用的一个授权策略,以及,基于我们采用 Cookie 授权的方式,结合目前互联网针对跨站点请求伪造 (CSRF) 攻击的防范要求,我们需要对网站的 Cookie 进行一些设定

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 定义授权策略
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// 无权访问的页路径
options.AccessDeniedPath = new PathString("/permission/forbidden"); // 登录路径
options.LoginPath = new PathString("/authentication/login"); // 登出路径
options.LogoutPath = new PathString("/authentication/logout"); // Cookie 过期时间(20 分钟)
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
}); // 配置 Cookie 策略
services.Configure<CookiePolicyOptions>(options =>
{
// 默认用户同意非必要的 Cookie
options.CheckConsentNeeded = context => true; // 定义 SameSite 策略,Cookies允许与顶级导航一起发送
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
}
}

如代码所示,在定义授权策略时,我们定义了三个重定向的页面,去告诉 Cookie 授权策略这里对应的页面在何处,同时,因为身份验证 Cookie 的默认过期时间会持续到关闭浏览器为止,也就是说,只要用户不点击退出按钮并且不关闭浏览器,用户会一直处于已经登录的状态,所以这里我们设定 20 分钟的过期时间,避免一些不必要的风险

至此,对于 Cookie 认证策略的配置就完成了,现在就可以在 Startup.Configure 方法中添加 UseAuthentication 中间件到 HTTP 管道中,实现对于网站认证的启用,这里需要注意,因为是先认证再授权,所以中间件的添加顺序不可以颠倒

public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting(); // 添加认证授权(顺序不可以颠倒)
//
app.UseAuthentication();
app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
}

此时,当我们再次访问系统时,因为没有经过认证,自动触发了重定向到系统登录页面的操作,而这里重定向跳转的页面就是上文代码中配置的 LoginPath 的属性值

2.3、登录、登出实现

当认证策略配置完成之后,就可以基于选择的策略来进行登录功能的实现。这里的登录页面上的按钮,模拟了一个登录表单提交,当点击之后会触发系统的认证逻辑,实现代码如下所示。这里别忘了将登录事件的 Action 上加上 AllowAnonymous 特性从而允许匿名访问

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginAsync()
{
// 1、Todo:校验账户、密码是否正确,获取需要的用户信息 // 2、创建用户声明信息
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "张三"),
new Claim(ClaimTypes.MobilePhone, "13912345678")
}; // 3、创建声明身份证
var claimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); // 4、创建声明身份证的持有者
var claimPrincipal = new ClaimsPrincipal(claimIdentity); // 5、登录
await HttpContext.SignInAsync(claimPrincipal); return Redirect("/");
}

在整块的代码中,涉及到三个主要的对象,ClaimClaimsIdentityClaimsPrincipal,通过对于这三个对象的使用,从而实现将用户登录成功后系统所需的用户信息包含在 Cookie 中

三个对象之间的区别,借用理解ASP.NET Core验证模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不读的英文博文这篇博客的解释来说明

  • Claim:被验证主体特征的一种表述,比如:登录用户名是...,email是...,用户Id是...,其中的“登录用户名”,“email”,“用户Id”就是 ClaimType
  • ClaimsIdentity:一组 claims 构成了一个 identity,具有这些 claims 的 identity 就是 ClaimsIdentity ,驾照就是一种 ClaimsIdentity,可以把 ClaimsIdentity理解为“证件”,驾照是一种证件,护照也是一种证件
  • ClaimsPrincipal:ClaimsIdentity 的持有者就是 ClaimsPrincipal ,一个 ClaimsPrincipal 可以持有多个 ClaimsIdentity,就比如一个人既持有驾照,又持有护照

最后,通过调用 HttpContext.SignInAsync 方法就可以完成登录功能,可以看到,当 Cookie 被清除后,用户也就处于登出的状态了,当然,我们也可以通过手动的调用 HttpContext.SignOutAsync 来实现登出

2.4、获取用户信息

对于添加在 Claim 中的信息,我们可以通过指定 ClaimType 的方式获取到,在 View 和 Controller 中,我们可以直接通过下面的方式进行获取,这里使用到的 User 其实就是上文中提到的 ClaimsPrincipal

var userName = User.FindFirst(ClaimTypes.Name)?.Value;

而当我们需要在一个独立的类库中获取存储的用户信息时,我们需要进行如下的操作

第一步,在 Startup.ConfigureServices 方法中注入 HttpContextAccessor 服务

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 注入 HttpContext
services.AddHttpContextAccessor(); }
}

第二步,在你需要使用的类库中通过 Nuget 引用 Microsoft.AspNetCore.Http,之后就可以在具体的类中通过注入 IHttpContextAccessor 来获取到用户信息,当然,也可以在此处实现登录、登出的方法

namespace Sample.Infrastructure
{
public interface ICurrentUser
{
string UserName { get; } Task SignInAsync(ClaimsPrincipal principal); Task SignOutAsync(); Task SignOutAsync(string scheme);
} public class CurrentUser : ICurrentUser
{
private readonly IHttpContextAccessor _httpContextAccessor; private HttpContext HttpContext => _httpContextAccessor.HttpContext; public CurrentUser(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
} public string UserName => HttpContext.User.FindFirst(ClaimTypes.Name)?.Value; public Task SignInAsync(ClaimsPrincipal principal) => HttpContext.SignInAsync(principal); public Task SignOutAsync() => HttpContext.SignOutAsync(); public Task SignOutAsync(string scheme) => HttpContext.SignOutAsync(scheme);
}
}

至此,整块的认证功能就已经实现了,希望对你有所帮助

Reference

  1. SameSite cookies
  2. Work with SameSite cookies in ASP.NET Core
  3. What does the CookieAuthenticationOptions.LogoutPath property do in ASP.NET Core 2.1?
  4. 理解ASP.NET Core验证模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不读的英文博文
  5. Introduction to Authentication with ASP.NET Core

在 ASP.NET Core 应用中使用 Cookie 进行身份认证的更多相关文章

  1. 三分钟学会在ASP.NET Core MVC 中使用Cookie

    一.Cookie是什么? 我的朋友问我cookie是什么,用来干什么的,可是我居然无法清楚明白简短地向其阐述cookie,这不禁让我陷入了沉思:为什么我无法解释清楚,我对学习的方法产生了怀疑!所以我们 ...

  2. .NET Core:在ASP.NET Core WebApi中使用Cookie

    一.Cookie的作用 Cookie通常用来存储有关用户信息的一条数据,可以用来标识登录用户,Cookie存储在客户端的浏览器上.在大多数浏览器中,每个Cookie都存储为一个小文件.Cookie表示 ...

  3. 采用最简单的方式在ASP.NET Core应用中实现认证、登录和注销

    在安全领域,认证和授权是两个重要的主题.认证是安全体系的第一道屏障,是守护整个应用或者服务的第一道大门.当访问者请求进入的时候,认证体系通过验证对方的提供凭证确定其真实身份.认证体系只有在证实了访问者 ...

  4. Asp.Net Core3.x中使用Cookie

    在Asp.Net中使用Cookie相对容易使用,Request和Response对象都提供了Cookies集合,要记住是从Response中存储,从Request中读取相应的cookie.Asp.Ne ...

  5. 理解ASP.NET Core - 基于Cookie的身份认证(Authentication)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 概述 通常,身份认证(Authentication)和授权(Authorization)都会放 ...

  6. 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?

    在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...

  7. ASP.NET Core MVC 中的 [Controller] 和 [NonController]

    前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...

  8. 如何在ASP.NET Core应用中实现与第三方IoC/DI框架的整合?

    我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上,它甚至提供了扩展点使我们可以与第三方DI框架进行整合.对此比较了解的读 ...

  9. ASP.NET Core MVC 中设置全局异常处理方式

    在asp.net core mvc中,如果有未处理的异常发生后,会返回http500错误,对于最终用户来说,显然不是特别友好.那如何对于这些未处理的异常显示统一的错误提示页面呢? 在asp.net c ...

随机推荐

  1. C语言丨博客作业03

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/zswxy/SE2020-3/ 这个作业要求在哪里 https://edu.cnblogs.com/campus/z ...

  2. 记一次MAVEN依赖事故

    笔者昨天遇到的背景是这样的  MAVEN A模块有一个子模块  需要依赖B模块下的一个子模块  我在B项目内通过mvn deploy上传子模块 但之后在A模块引用  怎么引用都不行  提示 org.a ...

  3. Linux 时间同步 03 ntpdate时间同步

    Linux 时间同步 03 ntpdate时间同步 目录 Linux 时间同步 03 ntpdate时间同步 安装ntpdate 修改/etc/sysconfig/ntpdate 使用ntpdate手 ...

  4. 茅坑杀手与Alias Method离散采样

    说起Alias,你可能第一个联想到的是Linux中的Alias命令,就像中世纪那些躲在茅坑下面(是真的,起码日本有粪坑忍者,没有马桶的年代就是社会的噩梦)进行刺杀的杀手一样,让人防不胜防,对于那些被这 ...

  5. Sql语句模糊查询字符串的两种写法

    Sql语句模糊查询有两种写法,一种是在jdbcTemplate的查询方法参数里拼接字符串%,一种是在Sql语句里拼接%字符串. public class IsNameDaoImpl implement ...

  6. Group by后加rollup、cube、Grouping_Sets的用法区别

    一.相关分析 通常当聚合率和数据量没有大于一定程度时,对于不涉及Rollup.Cube.Grouping_Sets这三种操作的聚合很少出现GC问题.对于Rollup.Cube.Grouping_Set ...

  7. Java的nanoTime()方法

    java有两个获取和时间相关的秒数方法,一个是广泛使用的 System.currentTimeMillis() 返回的是从一个长整型结果,表示毫秒. 另一个是 System.nanoTime() 返回 ...

  8. STM32 HAL库之串口详细篇

    一.基础认识 (一) 并行通信 原理:数据的各个位同时传输 优点:速度快 缺点:占用引脚资源多,通常工作时有多条数据线进行数据传输 8bit数据传输典型连接图: 传输的数据是二进制:11101010, ...

  9. 搭乘“AI大数据”快车,肌肤管家,助力美业数字化发展

    经过疫情的发酵,加速推动各行各业进入数据时代的步伐.美业,一个通过自身技术.产品让用户变美的行业,在AI大数据的加持下表现尤为突出. 对于美妆护肤企业来说,一边是进入存量市场,一边是疫后的复苏期,一边 ...

  10. .NET 5 程序高级调试-WinDbg

    上周和大家分享了.NET 5开源工作流框架elsa,程序跑起来后,想看一下后台线程的执行情况.抓了个进程Dump后,使用WinDbg调试,加载SOS调试器扩展,结果无法正常使用了: 0:000> ...