ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

原文: ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

 1、前言

  在涉及到后端项目的开发中,如何实现对于用户权限的管控是须要咱们首先考虑的,在实际开发过程当中,咱们可能会运用一些已经成熟的解决方案帮助咱们实现这一功能,而在 Grapefruit.VuCore 这个项目中,我将使用 Jwt 的方式实现对于用户的权限管控,在本章中,我将演示如何使用 Jwt 实现对于用户的受权、鉴权。html

  系列目录地址:ASP.NET Core 项目实战
  仓储地址:https://github.com/Lanesra712/Grapefruit.VuCore前端

 2、Step by Step

  一、一些概念

  Jwt(json web token),是一种基于 Json 的无状态受权令牌,由于 Jwt 是一种标准的数据传输规范,并非某家所独有的技术规范,所以很是适用于构建单点登陆服务,为 web、client、app 等等各类接口使用方提供受权服务。git

  在使用 Jwt 进行权限控制的过程当中,咱们须要先请求受权服务器获取到 token 令牌,将令牌存储到客户端本地(在 web 项目中,咱们能够将 token 存储到 localstorage 或是 cookie 中),以后,对于服务端的每一次请求,都须要将获取到的 token 信息添加到 http 请求的 header 中。github

$.ajax({ url: url, method: "POST", data: JSON.stringify(data), beforeSend: function (xhr) { /* Authorization header */ xhr.setRequestHeader("Authorization", "Bearer " + token); }, success: function (data) {} });

  当用户拥有令牌后是否就能够访问系统的全部功能了呢?答案固然否认的。对于一个系统来讲可能会有多种用户角色,每个用户角色能够访问的资源也是不一样的,因此,当用户已经拥有令牌后,咱们还须要对用户角色进行鉴定,从而作到对用户进行进一步的权限控制。web

  在 Grapefruit.VuCore 这个项目中,我采用的是基于策略的受权方式,经过定义一个受权策略来完善 Jwt 鉴权,以后将这个自定义策略注入到 IServiceCollection 容器中,对权限控制作进一步的完善,从而达到对于用户访问权限的管控。ajax

  基于策略的受权是微软在 ASP.NET Core 中添加的一种新的受权方式,经过定义好策略(policy)的一个或多个要求(requirements),将这个自定义的受权策略在 Startup.ConfigureServices 方法中做为受权服务配置的一部分进行注册以后便可按照咱们的策略处理程序进行权限的控制。json

services.AddAuthorization(options => { options.AddPolicy("Permission", policy => policy.Requirements.Add(new PolicyRequirement())); }) services.AddSingleton<IAuthorizationHandler, PolicyHandler>();

  就像我在后面的代码中同样,我定义了一个名叫 Permission 的受权策略,它包含了一个叫作 PolicyRequirement 的鉴权要求,在实现了受权策略后,将基于这个要求的鉴权方法 PolicyHandler 以单例(AddSingleton)的形式注入到服务集合中,此时,咱们就能够在须要添加验证的 controller 上添加 attribute 便可。后端

[Authorize(Policy = "Permission")] public class SecretController : ControllerBase {}

  二、受权

  在 Grapefruit.VuCore 这个项目中,涉及到受权相关的代码所在的位置我已在下图中进行标示。在以前系列开篇文章(ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js,搭建先后端分离框架)进行介绍整个项目框架时曾说到, Grapefruit.Application 是项目的应用层,顾名思义,就是为了实现咱们项目中的实际业务功能所划分的类库。所以,咱们实现 Jwt 的相关业务代码应该位于此层中。同时,由于对于 Jwt 的令牌颁发与鉴权,采用的是微软的 JwtBearer 组件,因此咱们在使用前须要先经过 Nuget 将引用添加到 Grapefruit.Application 上。api

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer Install-Package System.IdentityModel.Tokens.Jwt

  在 Grapefruit.Application 这个类库下我建立了一个  Authorization 解决方案文件夹用来存储受权相关的代码。在 Authorization 这个解决方案文件夹中包含了两个子文件夹 Jwt 和 Secret。Jwt 文件夹中主要包含咱们对于 Jwt 的操做,而 Secret 文件夹下则是对于用户的相关操做。缓存

  每一个子应用文件夹(Jwt、Secret)都包含了相同的结构:Dto 数据传输对象、功能接口,以及功能接口的实现类,这里接口的继承采用单继承的方式。

  在 Jwt 文件夹下建立一个 IJwtAppService 接口文件,在这里定义咱们对于 Jwt 的相关操做。由于对于 Jwt 的受权、鉴权是采用微软的 JwtBearer 组件,咱们只须要进行配置便可,因此这里只定义对于 token 的生成、刷新、停用,以及判断这个 token 是否有效这几个方法。同时,咱们须要建立 JwtAppService 这个类文件,去继承 IJwtAppService 从而实现接口功能。

public interface IJwtAppService { /// <summary>
    /// 新增 Jwt token /// </summary>
    /// <param name="dto">用户信息数据传输对象</param>
    /// <returns></returns>
 JwtAuthorizationDto Create(UserDto dto); /// <summary>
    /// 刷新 Token /// </summary>
    /// <param name="token">Token</param>
    /// <param name="dto">用户信息数据传输对象</param>
    /// <returns></returns>
    Task<JwtAuthorizationDto> RefreshAsync(string token, UserDto dto); /// <summary>
    /// 判断当前 Token 是否有效 /// </summary>
    /// <returns></returns>
    Task<bool> IsCurrentActiveTokenAsync(); /// <summary>
    /// 停用当前 Token /// </summary>
    /// <returns></returns>
 Task DeactivateCurrentAsync(); /// <summary>
    /// 判断 Token 是否有效 /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    Task<bool> IsActiveAsync(string token); /// <summary>
    /// 停用 Token /// </summary>
    /// <returns></returns>
    Task DeactivateAsync(string token); }

  JwtAuthorizationDto 是一个 token 信息的传输对象,包含咱们建立好的 token 相关信息,用来将 token 信息返回给前台进行使用。而 UserDto 则是用户登陆获取 token 时的数据传输对象,用来接收登陆时的参数值。

  在建立 token 或是验证 token 时,像 token 的颁发者、接收者之类的信息,由于会存在多个地方调用的可能性,这里我将这些信息存放在了配置文件中,后面当咱们须要使用的时候,只须要经过注入 IConfiguration 进行获取便可。关于 Jwt 的配置文件主要包含了四项:token 的颁发者,token 的接收者,加密 token 的 key 值,以及 token 的过时时间,你能够根据你本身的需求进行调整。

"Jwt": { "Issuer": "yuiter.com", "Audience": "yuiter.com", "SecurityKey": "a48fafeefd334237c2ca207e842afe0b", "ExpireMinutes": "20" }

  在 token 的建立过程当中能够简单拆分为三个部分:根据配置信息和用户信息建立一个 token,将加密后的用户信息写入到 HttpContext 上下文中,以及将建立好的 token 信息添加到静态的 HashSet<JwtAuthorizationDto> 集合中。

  在 token 建立、校验的整个生命周期中,都涉及到了  Scheme、Claim、ClaimsIdentity、ClaimsPrincipal 这些概念,若是你以前有使用过微软的 Identity 权限验证,对于这几个名词就会比较熟悉,可能某些小伙伴以前并无使用过 Identity,我来简单介绍下这几个名词的含义。

  Scheme 模式,这个与其他的名词相对独立,它主要是指明咱们是以什么受权方式进行受权的。例如,你是以 cookie 的方式受权或是以 OpenId 的方式受权,或是像这里咱们使用 Jwt Bearer 的方式进行受权。

  Claim 声明,以咱们的现实生活为例,咱们每一个人都会有身份证,上面会包含咱们的姓名、性别、民族、出生日期、家庭住址、身份证号,每一项数据的均可以当作是 type-value(数据类型-数据值),例如,姓名:张三。身份证上的每一项的信息就是咱们的 Claim 声明,姓名:张三,是一个 Claim;性别:男,也是一个 Claim。而对于 ClaimsIdentity,就像这一项项的信息最终组成了咱们的身份证,这一项项的 Claim 最终组成了咱们的 ClaimsIdentity。而 ClaimsPrincipal 则是 ClaimsIdentity 的持有者,就像咱们拥有身份证同样。

  从上面的文字能够总结出,Claim(每一项的证件信息)=》ClaimsIdentity(证件)=》ClaimsPrincipal(证件持有者)。其中,一个 ClaimsIdentity 能够包含多个的 Claim,而一个 ClaimsPrincipal 能够包含多个的 ClaimsIdentity。

   若是想要深刻了解 ASP.NET Core 的受权策略的能够看看园子里这篇文章 =》ASP.NET Core 运行原理解剖[5]:Authentication,或是国外的这篇介绍 ASP.NET Core 受权的文章 =》Introduction to Authentication with ASP.NET Core

  实现 token 生成的最终代码实现以下所示,能够看到,在建立 ClaimsIdentity “证件”信息时,我添加了用户的角色信息,并把加密后的用户信息写入到 HttpContext 上下文中,这样,咱们在后面验证的时候就能够经过 HttpContext 获取到用户的角色信息,从而判断用户是否能够访问当前请求的地址。

/// <summary>
/// 新增 Token /// </summary>
/// <param name="dto">用户信息数据传输对象</param>
/// <returns></returns>
public JwtAuthorizationDto Create(UserDto dto) { JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecurityKey"])); DateTime authTime = DateTime.UtcNow; DateTime expiresAt = authTime.AddMinutes(Convert.ToDouble(_configuration["Jwt:ExpireMinutes"])); //将用户信息添加到 Claim 中
    var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); IEnumerable<Claim> claims = new Claim[] { new Claim(ClaimTypes.Name,dto.UserName), new Claim(ClaimTypes.Role,dto.Role.ToString()), new Claim(ClaimTypes.Email,dto.Email), new Claim(ClaimTypes.Expiration,expiresAt.ToString()) }; identity.AddClaims(claims); //签发一个加密后的用户信息凭证,用来标识用户的身份
    _httpContextAccessor.HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims),//建立声明信息
        Issuer = _configuration["Jwt:Issuer"],//Jwt token 的签发者
        Audience = _configuration["Jwt:Audience"],//Jwt token 的接收者
        Expires = expiresAt,//过时时间
        SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)//建立 token
 }; var token = tokenHandler.CreateToken(tokenDescriptor); //存储 Token 信息
    var jwt = new JwtAuthorizationDto { UserId = dto.Id, Token = tokenHandler.WriteToken(token), Auths = new DateTimeOffset(authTime).ToUnixTimeSeconds(), Expires = new DateTimeOffset(expiresAt).ToUnixTimeSeconds(), Success = true }; _tokens.Add(jwt); return jwt; }

  当建立好 token 以后,客户端就能够在 Http 请求的 header 中添加 token 信息,从而访问受保护的资源。不过,在某些状况下,好比说,用户修改了密码以后,虽然当前的 token 信息可能还未过时,但咱们也不能容许用户再使用当前的 token 信息进行接口的访问,这时,就涉及到了对于 token 信息的停用以及刷新。

  这里我采用是当咱们停用 token 信息时,将停用的 token 信息添加到 Redis 缓存中,以后,在用户请求时判断这个 token 是否是存在于 Redis 中便可。

  固然,你也能够在停用当前用户的 token 信息时,将 HashSet 中的这个 token 信息进行删除,以后,经过判断访问时的 token 信息是否在 HashSet 集合中,判断 token 是否有效。

  方法不少,看你本身的需求了。

  对于 Redis 的读写操做,我是使用微软的 Redis 组件进行的,你能够按照你的喜爱进行修改。若是你和我同样,采用这个组件,你须要在 Grapefruit.Application 这个类库中经过 Nuget 添加微软的分布式缓存抽象接口 dll 的引用,以及在 Grapefruit.WebApi 项目中添加微软的 Redis 实现。

Install-Package Microsoft.Extensions.Caching.Abstractions ## 分布式缓存抽象接口
Install-Package Microsoft.Extensions.Caching.Redis ## Redis 实现

  当咱们停用 token 时,经过 HttpContext 上下文获取到 HTTP Header 中的 token 信息,将该 token 信息存储到 Redis 缓存中,这样,咱们就完成了对于 token 的停用。

public class JwtAppService : IJwtAppService { /// <summary>
    /// 停用 Token /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    public async Task DeactivateAsync(string token) => await _cache.SetStringAsync(GetKey(token), " ", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(Convert.ToDouble(_configuration["Jwt:ExpireMinutes"])) }); /// <summary>
    /// 停用当前 Token /// </summary>
    /// <returns></returns>
    public async Task DeactivateCurrentAsync() => await DeactivateAsync(GetCurrentAsync()); /// <summary>
    /// 设置缓存中过时 Token 值的 key /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    private static string GetKey(string token) => $"deactivated token:{token}"; /// <summary>
    /// 获取 HTTP 请求的 Token 值 /// </summary>
    /// <returns></returns>
    private string GetCurrentAsync() { //http header
        var authorizationHeader = _httpContextAccessor .HttpContext.Request.Headers["authorization"]; //token
        return authorizationHeader == StringValues.Empty ? string.Empty : authorizationHeader.Single().Split(" ").Last();// bearer tokenvalue
 } }

  对于 token 的刷新,其实咱们就能够当作从新生成了一个 token 信息,只不过咱们须要将以前的 token 信息进行停用。

public class JwtAppService : IJwtAppService { /// <summary>
    /// 刷新 Token /// </summary>
    /// <param name="token">Token</param>
    /// <param name="dto">用户信息</param>
    /// <returns></returns>
    public async Task<JwtAuthorizationDto> RefreshAsync(string token, UserDto dto) { var jwtOld = GetExistenceToken(token); if (jwtOld == null) { return new JwtAuthorizationDto() { Token = "未获取到当前 Token 信息", Success = false }; } var jwt = Create(dto); //停用修改前的 Token 信息
        await DeactivateCurrentAsync(); return jwt; } /// <summary>
    /// 判断是否存在当前 Token /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    private JwtAuthorizationDto GetExistenceToken(string token) => _tokens.SingleOrDefault(x => x.Token == token); }

  至此,咱们对于 token 的建立、刷新、停用的代码就已经完成了,接下来,咱们来实现对于 token 信息的验证。PS:下面的代码如无特殊说明外,均位于 Startup 类中。

  三、鉴权

  在 ASP.NET Core 应用中,依赖注入随处可见,而咱们对于咱们的功能方法的使用,也是采用依赖注入到容器,经过功能接口进行调用的方式。所以,咱们须要将咱们的接口与其实现类注入到 IServiceCollection 容器中。这里,咱们采用反射的方式,批量的将程序集内的接口与其实现类进行注入。

public void ConfigureServices(IServiceCollection services) { Assembly assembly = Assembly.Load("Grapefruit.Application"); foreach (var implement in assembly.GetTypes()) { Type[] interfaceType = implement.GetInterfaces(); foreach (var service in interfaceType) { services.AddTransient(service, implement); } } }

  由于基础的权限验证咱们是采用的微软的 JwtBearer 权限验证组件进行的受权和鉴权,所以对于 token 信息的基础鉴权操做,只须要咱们在中间件中进行配置便可。同时,咱们也在 IJwtAppService 接口中定义了对于 token 信息的一些操做,而对于咱们自定义的权限验证策略,则须要经过基于策略的受权方式进行实现。

  首先,咱们须要先定义一个继承于 IAuthorizationRequirement 的自定义受权要求类 PolicyRequirement。在这个类中,你能够定义一些属性,经过有参构造函数的方式进行构造,这里我不定义任何的属性,仅是建立这个类。

public class PolicyRequirement : IAuthorizationRequirement { }

  当咱们建立好 PolicyRequirement 这个权限要求类后,咱们就能够经过继承 AuthorizationHandler 来实现咱们的受权逻辑。这里实现权限控制的代码逻辑,主要是经过重写 HandleRequirementAsync 方法来实现的。

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement) { //Todo:获取角色、Url 对应关系
    List<Menu> list = new List<Menu> { new Menu { Role = Guid.Empty.ToString(), Url = "/api/v1.0/Values" }, new Menu { Role=Guid.Empty.ToString(), Url="/api/v1.0/secret/deactivate" }, new Menu { Role=Guid.Empty.ToString(), Url="/api/v1.0/secret/refresh" } }; var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext; //获取受权方式
    var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { //验证签发的用户信息
        var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); if (result.Succeeded) { //判断是否为已停用的 Token
            if (!await _jwtApp.IsCurrentActiveTokenAsync()) { context.Fail(); return; } httpContext.User = result.Principal; //判断角色与 Url 是否对应 //             var url = httpContext.Request.Path.Value.ToLower(); var role = httpContext.User.Claims.Where(c => c.Type == ClaimTypes.Role).FirstOrDefault().Value; var menu = list.Where(x => x.Role.Equals(role) && x.Url.ToLower().Equals(url)).FirstOrDefault(); if (menu == null) { context.Fail(); return; } return; } } context.Fail(); }

  在判断用户是否能够访问当前的请求地址时,首先须要获取到用户角色与其容许访问的地址列表,这里我使用的是模拟的数据。经过判断当前登陆用户的角色是否包含请求的地址,当用户的角色并不包含对于访问地址的权限时,返回 403 Forbidden 状态码。

  这里须要注意,若是你准备采起 RESTful 风格的 API,由于请求的地址是相同的,你须要添加一个 HTTP 谓词参数用来指明所请求的方法,从而达到访问权限管控的目的。。

  包含 token 的基础验证的受权配置的代码以下所示。在中间件进行 Jwt 验证的过程当中,会验证受权方式是否是 Bearer 以及经过 token 的属性解密以后与生成时用户数据进行比对,从而判断这个 token 是否有效。

public void ConfigureServices(IServiceCollection services) { string issuer = Configuration["Jwt:Issuer"]; string audience = Configuration["Jwt:Audience"]; string expire = Configuration["Jwt:ExpireMinutes"]; TimeSpan expiration = TimeSpan.FromMinutes(Convert.ToDouble(expire)); SecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecurityKey"])); services.AddAuthorization(options => { //一、Definition authorization policy
        options.AddPolicy("Permission", policy => policy.Requirements.Add(new PolicyRequirement())); }).AddAuthentication(s => { //二、Authentication
        s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(s => { //三、Use Jwt bearer 
        s.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = issuer, ValidAudience = audience, IssuerSigningKey = key, ClockSkew = expiration, ValidateLifetime = true }; s.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { //Token expired
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return Task.CompletedTask; } }; }); //DI handler process function
    services.AddSingleton<IAuthorizationHandler, PolicyHandler>(); }

  由于咱们是使用 Swagger 进行的 API 文档的可视化,这里,咱们继续配置 Swagger 从而使 Swagger 能够支持咱们的权限验证方式。

public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(s => { //Add Jwt Authorize to http header
        s.AddSecurityDefinition("Bearer", new ApiKeyScheme { Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", Name = "Authorization",//Jwt default param name
            In = "header",//Jwt store address
            Type = "apiKey"//Security scheme type
 }); //Add authentication type
        s.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> { { "Bearer", new string[] { } } }); }); }

  在停用 token 的代码中,咱们使用了 Redis 去保存停用的 token 信息,所以,咱们须要配置咱们的 Redis 链接。

public void ConfigureServices(IServiceCollection services) { services.AddDistributedRedisCache(r => { r.Configuration = Configuration["Redis:ConnectionString"]; }); }

  如今,整个业务相关的代码已经完成了,咱们能够建立前端访问的接口了。这里我是在 Controllers 下的 V1 文件夹下建立了一个 SecretController 用来构建前端访问的接口。控制器中主要有三个方法,分别为 CancelAccessToken(停用 token)、Login(获取 token)以及 RefreshAccessTokenAsync(刷新 token)。

public class SecretController : ControllerBase { /// <summary>
    /// 停用 Jwt 受权数据 /// </summary>
    /// <returns></returns>
    [HttpPost("deactivate")] public async Task<IActionResult> CancelAccessToken() { await _jwtApp.DeactivateCurrentAsync(); return Ok(); } /// <summary>
    /// 获取 Jwt 受权数据 /// </summary>
    /// <param name="dto">受权用户信息</param>
    [HttpPost("token")] [AllowAnonymous] public IActionResult Login([FromBody] SecretDto dto) { //Todo:获取用户信息
        var user = new UserDto { Id = Guid.NewGuid(), UserName = "yuiter", Role = Guid.Empty, Email = "yuiter@yuiter.com", Phone = "13912345678", }; if (user == null) return Ok(new JwtResponseDto { Access = "无权访问", Type = "Bearer", Profile = new Profile { Name = dto.Account, Auths = 0, Expires = 0 } }); var jwt = _jwtApp.Create(user); return Ok(new JwtResponseDto { Access = jwt.Token, Type = "Bearer", Profile = new Profile { Name = user.UserName, Auths = jwt.Auths, Expires = jwt.Expires } }); } /// <summary>
    /// 刷新 Jwt 受权数据 /// </summary>
    /// <param name="dto">刷新受权用户信息</param>
    /// <returns></returns>
    [HttpPost("refresh")] public async Task<IActionResult> RefreshAccessTokenAsync([FromBody] SecretDto dto) { //Todo:获取用户信息
        var user = new UserDto { Id = Guid.NewGuid(), UserName = "yuiter", Role = Guid.Empty, Email = "yuiter@yuiter.com", Phone = "13912345678", }; if (user == null) return Ok(new JwtResponseDto { Access = "无权访问", Type = "Bearer", Profile = new Profile { Name = dto.Account, Auths = 0, Expires = 0 } }); var jwt = await _jwtApp.RefreshAsync(dto.Token, user); return Ok(new JwtResponseDto { Access = jwt.Token, Type = "Bearer", Profile = new Profile { Name = user.UserName, Auths = jwt.Success ? jwt.Auths : 0, Expires = jwt.Success ? jwt.Expires : 0 } }); } }

  如今,让咱们测试一下,从下图中能够看到,当咱们未获取 token 时,访问接口提示咱们 401 Unauthorized,当咱们模拟登陆获取到 token 信息后,再次访问受保护的资源时,已经能够获取到响应的数据。以后,当咱们刷新 token,此时再用原来的 token 信息访问时,已经没法访问,提示 403 Forbidden,同时,能够看到咱们的 Redis 中已经存在了停用的 token 信息,此时,使用新的 token 信息又能够访问了。

  至此,整个的 Jwt 受权鉴权相关的代码就已经完成了,由于篇幅缘由,完整的代码请到 Github 上进行查看(电梯直达)。PS:由于博客园容许上传的图片限制最大尺寸为 10M,因此这里上传的 gif 是压缩后的,见谅见谅,若是有须要查看清晰的图片,欢迎到个人我的博客上查看(电梯直达)。

 3、总结

   本章,主要是使用 Jwt 完成对于用户的受权与鉴权,实现了对于用户 token 令牌的建立、刷新、停用以及校验。在实际的开发中,采用成熟的轮子多是更好的方案,若是你有针对 Jwt 进行用户受权、鉴权更好的解决方案的话,欢迎你在评论区留言指出。拖了好久,应该是年前的最后一篇了,提早祝你们新年快乐哈~~~

相关文章
相关标签/搜索