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. 平滑算法:三次样条插值(Cubic Spline Interpolation)

    https://blog.csdn.net/left_la/article/details/6347373 感谢强大的google翻译. 我从中认识到了航位推算dead reckoning,立方体样条 ...

  2. ADO.NET对SqlServer进行简单的增删改查

    对数据库进行增删改查,首先想到的应该就是连接字符串了. 我们的连接字符串是由"Server=地址(本机=local);Database=数据库名称;User Id=登陆用户名;Passwor ...

  3. 第十六章节 BJROBOT 开机自启动服务【ROS全开源阿克曼转向智能网联无人驾驶车】

    1.把小车平放在地板上,用资料里的虚拟机,打开一个终端 ssh 过去主控端运行rosrun robot_upstart install znjrobot/launch/bringup.launch 2 ...

  4. 达梦数据库学习(一、linux操作系统安装及数据库安装)

    达梦数据库学习(一.linux操作系统安装及数据库安装) 环境介绍: 使用VM12+中标麒麟V7.0操作系统+达梦8数据库 一.linux系统搭建 本部分没有需要着重介绍,注意安装时基本环境选择&qu ...

  5. 一张图看懂sql的各种join

    下图展示了 LEFT JOIN.RIGHT JOIN.INNER JOIN.OUTER JOIN 相关的 7 种用法.

  6. python之json、pickle模块

    一.json模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不过,eval方法是有局限性的,对于普通的数据类型,json.loads和eval都能用,但遇到特殊类型的时候, ...

  7. 初学VBA

    一个最基本的VBA程序 Sub test() //宏开始 Dim ge As Range //定义变量 For Each ge In Range("a1:a10") //从a1到a ...

  8. 原生js的一些盲点

    1.document.readyState 有三个值loading  interactive //loading 仍在加载 interactive  文档已被解析 正在加载状态结束 但是样式表和框架之 ...

  9. transmission protocol

    传输层主要定义了主机应用程序间端到端的连通性,它一般包含四项基本功能 . 将应用层发往网络层的数据分段或将网络层发往应用层的数据段合并 建立端到端的链接,主要是建立逻辑连接以传送数据流 将数据段从一台 ...

  10. 关于.NET中的控制反转(三)- 依赖注入之 Autofac

    一.Autofac简介 Autofac和其他容器的不同之处是它和C#语言的结合非常紧密,在使用过程中对你的应用的侵入性几乎为零,更容易与第三方的组件集成.Autofac的主要特性如下: 组件侵入性为零 ...