小言_互联网的博客

ASP.NET Core教程-Configuration(配置)-JWT

762人阅读  评论(0)

更新记录
转载请注明出处:
2022年11月10日 发布。
2022年11月8日 从笔记迁移到博客。

JWT 基础

JWT 是什么(What is JWT)

JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。JWT的官网地址:https://jwt.io/。通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT令牌在api接口中校验用户的身份以确认用户是否有访问api的权限。JWT中包含了身份认证必须的参数以及用户自定义的参数,JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。JWT stands for JSON Web Token. This token is used to create an access token for an application. JWT is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. The purpose of using JWT is not to hide data but to ensure the user’s authenticity that is requesting the data. JWT is signed and encoded, not encrypted.

JWT 作用、适合场景

  1. 授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
  2. 信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。

JWT 与传统 Session 身份校验对比

传统 Session 身份验证扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。如果session存储的节点挂了,那么整个服务都会瘫痪,体验相当不好,风险也很高。

相比之下,JWT 的实现方式是将用户信息存储在客户端,服务端不进行保存。每次请求都把令牌带上以校验用户登录状态,这样服务就变成了无状态的,服务器集群也很好扩展。

JWT 令牌结构(JWT Structure)

在紧凑的形式中,JSON Web Tokens由dot(.)分隔的三个部分组成,它们是:

  • Header 头
  • Payload 有效载荷
  • Signature 签名

That is a header, payload and a signature . This 3 parts is separated by a columns.

HEADER.PAYLOAD.SIGNATURE

Header 部分
Header 部分是一个JSON对象,主要包含 token 的元数据,标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法。例如:HMAC SHA256或RSA。实例:


   
  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

Payload 部分
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。主要包含声明信息(Claims),是整个 JWT 中的核心数据。这些声明数据是key-value对的数据结构。通常如用户名,角色等信息,过期日期等,注意:数据是未加密的,不建议存放敏感信息。

Claims的实体一般包含用户和一些元数据,这些claims分成三种类型:

  • reserved claims:预定义的 一些声明,并不是强制的但是推荐,它们包括 iss (issuer), exp (expiration time), sub (subject),aud(audience) 等(这里都使用三个字母的原因是保证 JWT 的紧凑)。
  • public claims: 公有声明,这个部分可以随便定义,但是要注意和 IANA JSON Web Token 冲突。
  • private claims: 私有声明,这个部分是共享被认定信息中自定义部分。

Payload 实例:

{"name":"admin","exp":1578645536,"iss":"webapi.cn","aud":"WebApi"}

JWT 规定了7个官方标准字段:


   
  1. iss (issuer):签发人
  2. exp (expiration time):过期时间
  3. sub (subject):主题
  4. aud (audience):受众
  5. nbf (Not Before):生效时间
  6. iat (Issued At):签发时间
  7. jti (JWT ID):编号

除了官方字段,还可以在这个部分定义私有字段:


   
  1. {
  2. "sub": "1234567890",
  3. "name": "John Doe",
  4. "admin": true
  5. }

Signature 部分
Signature 部分是对 Header 和 Payload 两部分的签名,防止数据篡改。签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。生成签名也很简单,首先,需要指定一个密钥(Secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。加密过程如下。


   
  1. HMACSHA256(
  2. base64UrlEncode(Header) + "." +
  3. base64UrlEncode(Payload),
  4. Secret)

签名生成后,使用 Base64进行编码,使用.号将三个部分连接起来即可。输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递。

Base64(Header).Base64(Payload).Signature

JSON Web令牌如何工作?

在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web令牌。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,不应该将令牌保留的时间超过要求。每当用户想要访问受保护的路由或资源时,用户代理应该使用承载模式发送 JWT,通常在 Authorization标头中。标题的内容应如下所示:

Authorization: Bearer <token>

在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查Authorization标头中的有效JWT ,如果存在,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。如果在标Authorization头中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。

下图显示了如何获取JWT并用于访问API或资源:

  1. 应用程序向授权服务器请求授权
  2. 校验用户身份,校验成功,返回token
  3. 应用程序使用访问令牌访问受保护的资源

认证(authentication)与授权(authorization)

认证(authentication)与授权(authorization),它们经常在一起工作,所以有时候会分不清楚。并且这两个英文单词长得也像兄弟。举例来说,我刷门禁卡进入公司,门禁【认证】了我是这里的员工,可以进入;但进入公司以后,我并不是所有房间都可以进,比如“机房重地,闲人免进”,我能进入哪些房间,需要公司的【授权】。这就是认证和授权的区别。

ASP.NET Core 配置 JWT

JWT 认证授权流程

本质就是将用户的 Id 或者唯一编号放在 Token 中,每次请求都会发送到服务器端(消费端)。服务器端拿到 Token 解析后,拿到用户的唯一 Id,然后用认证功能去进行对该 Id 处理,比如获得该 Id 对应的用户是否有指定 Role 的权限、单独的权限等。如果有权限,则放行该请求,否则拒绝该请求。

需要用到的包

Microsoft.AspNetCore.Authentication.JwtBearer
官方地址:https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer/

安装包(Install JWT Packages)


   
  1. dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
  2. dotnet add package System.IdentityModel.Tokens.Jwt

建立 Authorization 文件夹存放认证相关的特性

创建 AllowAnonymousAttribute.cs 文件

自定义 [AllowAnonymous] 属性用于允许匿名访问使用 [Authorize] 属性修饰的控制器的指定操作方法。 它用于用户控制器以允许匿名访问注册和登录操作方法。 如果操作方法使用 [AllowAnonymous] 修饰,则会跳过授权认证。


   
  1. using System;
  2. namespace PandaCMS.Authorization
  3. {
  4. /// <summary>
  5. /// 允许匿名访问
  6. /// </summary>
  7. [AttributeUsage(AttributeTargets.Method)]
  8. public class AllowAnonymousAttribute : Attribute
  9. { }
  10. }

创建 AuthorizeAttribute.cs 文件

自定义 [Authorize] 属性用于限制对控制器或指定操作方法的访问。 只有经过授权的请求才能访问使用 [Authorize] 属性修饰的操作方法。当控制器用 [Authorize] 属性修饰时,所有操作方法都仅限于授权请求,除了用自定义 [AllowAnonymous] 属性修饰的方法。

​ 授权由 OnAuthorization 方法执行,该方法检测的内容是可以自定义的,通过则可以访问控制器和动作,不通过则返回 401 Unauthorized 响应。下面代码中主要验证了从JwtMiddleware中传来的context.HttpContext.Items["User"]是否存在,用户是否有访问指定URL的权限。


   
  1. using Microsoft.AspNetCore.Http;
  2. using Microsoft.AspNetCore.Mvc;
  3. using Microsoft.AspNetCore.Mvc.Filters;
  4. using System;
  5. using System.Linq;
  6. using PandaCMS.Data.Models.UserManagement;
  7. using PandaCMS.Authorization;
  8. using PandaCMS.ServiceInterface.UserManagement;
  9. namespace PandaCMS.Authorization
  10. {
  11. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  12. public class AuthorizeAttribute : Attribute, IAuthorizationFilter
  13. {
  14. /// <summary>
  15. /// 开始验证
  16. /// </summary>
  17. /// <param name="context"></param>
  18. public void OnAuthorization (AuthorizationFilterContext context)
  19. {
  20. //如果有[AllowAnonymous]修饰,则跳过验证
  21. var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
  22. if (allowAnonymous)
  23. {
  24. return;
  25. }
  26. //获得在JWT中间件中设置的UserId值
  27. object userId = context.HttpContext.Items[ "UserId"];
  28. //如果UserId值不存在,就返回权限不足
  29. if (userId == null)
  30. {
  31. context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
  32. return;
  33. }
  34. //用户请求的Url
  35. var requestUrl = context.HttpContext.Request.Path.Value;
  36. //获得用户服务
  37. IUserService userService = (IUserService)context.HttpContext.RequestServices.GetService(typeof(IUserService));
  38. //权限验证
  39. bool havePermission = userService.CheckUserHaveApiPermission(Guid.Parse(userId.ToString()), requestUrl);
  40. if (!havePermission)
  41. {
  42. //无权限,就返回权限不足
  43. context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
  44. return;
  45. }
  46. }
  47. }
  48. }

建立Middleware文件夹存放认证中间件

创建JwtMiddleware.cs 文件

自定义 JWT 中间件从请求 Authorization 标头(如果有)中提取 JWT 令牌,并使用 jwtService.ValidateToken(token) 方法对其进行验证。 如果验证成功,则返回令牌中的用户 ID,并将经过身份验证的用户对象附加到 HttpContext.Items 集合以使其可在当前请求的范围内访问。如果令牌验证失败或没有令牌,则仅允许请求访问公共(匿名)路由,因为没有附加到 HTTP 上下文的经过身份验证的用户对象。 检查用户对象是否附加的授权逻辑位于自定义授权属性中,如果授权失败则返回 401 Unauthorized 响应。


   
  1. using Microsoft.AspNetCore.Http;
  2. using Microsoft.Extensions.Options;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using PandaCMS.ServiceInterface.UserManagement;
  6. using PandaCMS.Service.UserManagement;
  7. using PandaCMS.ServiceInterface.AuthorizationManagement;
  8. namespace PandaCMS.Authorization
  9. {
  10. /// <summary>
  11. /// JWT权限验证中间件
  12. /// </summary>
  13. public class JwtMiddleware
  14. {
  15. private readonly RequestDelegate _next;
  16. public JwtMiddleware (RequestDelegate next)
  17. {
  18. _next = next;
  19. }
  20. /// <summary>
  21. /// 执行中间件
  22. /// </summary>
  23. /// <param name="context"></param>
  24. /// <param name="userService"></param>
  25. /// <param name="jwtService"></param>
  26. /// <returns></returns>
  27. public async Task Invoke (HttpContext context, IUserService userService, IJwtService jwtService)
  28. {
  29. //从HTTP Header中提取Authorization参数(Token)
  30. string token = context.Request.Headers[ "Authorization"].FirstOrDefault()?.Split( " ").Last() ?? "";
  31. //如果Token不存在,则直接跳过
  32. if( string.IsNullOrWhiteSpace(token))
  33. {
  34. await _next(context);
  35. }
  36. //先检查服务器段是否已经废除了该Token
  37. //如果废除了,就直接跳过
  38. if (jwtService.TokenIsBeInvalidationByServer(token))
  39. {
  40. await _next(context);
  41. }
  42. //验证Token
  43. if (!jwtService.ValidateToken(token))
  44. {
  45. await _next(context);
  46. }
  47. //验证成功,把用户Id放在HTTP上下文对象中
  48. context.Items[ "UserId"] = jwtService.GetUserId();
  49. await _next(context);
  50. }
  51. }
  52. }

建立Services 文件夹存放用户和认证相关的服务

创建 JwtService 服务

JwtService 类包含用于生成和验证 JWT 令牌的方法。GenerateToken() 方法生成一个 JWT 令牌。ValidateToken() 方法用于验证 JWT


   
  1. using Microsoft.Extensions.Options;
  2. using Microsoft.IdentityModel.Tokens;
  3. using System;
  4. using System.IdentityModel.Tokens.Jwt;
  5. using System.Linq;
  6. using System.Security.Claims;
  7. using System.Text;
  8. using PandaCMS.Data.Models.UserManagement;
  9. using PandaCMS.ServiceInterface.AuthorizationManagement;
  10. using Microsoft.Extensions.Configuration;
  11. using PandaCMS.Data.DbContexts;
  12. using PandaCMS.Data.Models.AuthorizationManagement;
  13. namespace PandaCMS.Service.AuthorizationManagement
  14. {
  15. /// <summary>
  16. /// JWT认证服务
  17. /// </summary>
  18. public class JwtService : IJwtService
  19. {
  20. /// <summary>
  21. /// 配置对象(注入)
  22. /// </summary>
  23. private IConfiguration Configuration { get; set; }
  24. /// <summary>
  25. /// 数据库上下文(注入)
  26. /// </summary>
  27. private PandaCMSDbContext PandaCMSDbContext { get; set; }
  28. /// <summary>
  29. /// 用户Id
  30. /// </summary>
  31. public Guid UserId { get; private set; }
  32. /// <summary>
  33. /// JWT Token字符串
  34. /// </summary>
  35. public string JwtToken { get; private set; }
  36. /// <summary>
  37. /// 构造函数
  38. /// </summary>
  39. /// <param name="configuration"></param>
  40. public JwtService (IConfiguration configuration, PandaCMSDbContext pandaCMSDbContext)
  41. {
  42. //初始化
  43. this.Configuration = configuration;
  44. this.PandaCMSDbContext = pandaCMSDbContext;
  45. }
  46. /// <summary>
  47. /// 生成Token
  48. /// </summary>
  49. /// <param name="userId"></param>
  50. /// <returns></returns>
  51. public string GenerateToken (Guid userId)
  52. {
  53. // generate token that is valid for 7 days
  54. var tokenHandler = new JwtSecurityTokenHandler();
  55. //JWT key
  56. var key = Encoding.ASCII.GetBytes(this.Configuration[ "Authorization:JwtAuth:Key"]);
  57. //Token 描述
  58. var tokenDescriptor = new SecurityTokenDescriptor
  59. {
  60. //Token负载
  61. Subject = new ClaimsIdentity(new[] {
  62. //用户Id
  63. new Claim( "UserId", userId.ToString()),
  64. }),
  65. //发布人
  66. Issuer = this.Configuration[ "Authorization:JwtAuth:Issuer"],
  67. //受众人
  68. Audience = this.Configuration[ "Authorization:JwtAuth:Audience"],
  69. //过期时间
  70. Expires = DateTime.UtcNow.AddDays( int.Parse(this.Configuration[ "Authorization:JwtAuth:ExpiresDay"])),
  71. //签名
  72. SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
  73. };
  74. //创建Token字符串
  75. var token = tokenHandler.CreateToken(tokenDescriptor);
  76. return tokenHandler.WriteToken(token);
  77. }
  78. /// <summary>
  79. /// 验证Token
  80. /// </summary>
  81. /// <param name="token"></param>
  82. /// <returns></returns>
  83. public bool ValidateToken (string token)
  84. {
  85. //如果 Token 是空的直接返回
  86. if (token == null)
  87. {
  88. return false;
  89. }
  90. //验证 Token
  91. var tokenHandler = new JwtSecurityTokenHandler();
  92. var key = Encoding.ASCII.GetBytes(this.Configuration[ "Authorization:JwtAuth:Key"]);
  93. try
  94. {
  95. tokenHandler.ValidateToken(token, new TokenValidationParameters
  96. {
  97. ValidateIssuerSigningKey = true,
  98. IssuerSigningKey = new SymmetricSecurityKey(key),
  99. //验证签发者
  100. ValidateIssuer = true,
  101. //验证使用者
  102. ValidateAudience = true,
  103. //签发者信息
  104. ValidIssuer = this.Configuration[ "Authorization:JwtAuth:Issuer"],
  105. //使用者信息
  106. ValidAudience = this.Configuration[ "Authorization:JwtAuth:Audience"],
  107. //验证Token生命周期
  108. ValidateLifetime = true,
  109. // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
  110. ClockSkew = TimeSpan.Zero
  111. }, out SecurityToken validatedToken);
  112. //转成Token对象
  113. var jwtToken = (JwtSecurityToken)validatedToken;
  114. //获得Token中的用户Id
  115. this.UserId = Guid.Parse(jwtToken.Claims.First(x => x.Type == "UserId").Value);
  116. //设置Token字符串
  117. this.JwtToken = token;
  118. // return user id from JWT token if validation successful
  119. return true;
  120. }
  121. catch
  122. {
  123. // return false if validation fails
  124. return false;
  125. }
  126. }
  127. /// <summary>
  128. /// 获得用户Id
  129. /// </summary>
  130. /// <returns></returns>
  131. public Guid GetUserId ()
  132. {
  133. return this.UserId;
  134. }
  135. /// <summary>
  136. /// 获得Token字符串
  137. /// </summary>
  138. /// <returns></returns>
  139. public string GetToken ()
  140. {
  141. return this.JwtToken;
  142. }
  143. /// <summary>
  144. /// 使指定的Token失效
  145. /// </summary>
  146. /// <param name="jwtToken">Token字符串</param>
  147. /// <returns></returns>
  148. public async Task< bool> InvalidationUserTokenByServer (string jwtToken)
  149. {
  150. //把Token存入数据库,表示失效
  151. this.PandaCMSDbContext.JwtTokenInvaildModels
  152. .Add(new JwtTokenInvaildModel() {
  153. InvalidToken = jwtToken, Id = new Guid()
  154. });
  155. return await this.PandaCMSDbContext.SaveChangesAsync() > 0;
  156. }
  157. /// <summary>
  158. /// 检测Token是否已经被废除了
  159. /// </summary>
  160. /// <param name="jwtToken">Token字符串</param>
  161. /// <returns></returns>
  162. public bool TokenIsBeInvalidationByServer (string jwtToken)
  163. {
  164. //检测是否在失效的模型中存在
  165. bool isInvalid = this.PandaCMSDbContext.JwtTokenInvaildModels
  166. .Where(item => item.InvalidToken == jwtToken)
  167. .Count() > 0;
  168. return isInvalid;
  169. }
  170. }
  171. }

创建 UserService 服务


   
  1. using PandaCMS.Data.Models.UserManagement;
  2. using PandaCMS.ServiceInterface.UserManagement;
  3. using PandaCMS.Data.Models.AuthorizationManagement;
  4. using Microsoft.EntityFrameworkCore;
  5. using Microsoft.EntityFrameworkCore.SqlServer;
  6. using PandaCMS.Data.DbContexts;
  7. using PandaCMS.Data.Models.UserManagement;
  8. using PandaCMS.ServiceInterface.AuthorizationManagement;
  9. namespace PandaCMS.Service.UserManagement
  10. {
  11. /// <summary>
  12. /// 用户服务
  13. /// </summary>
  14. public class UserService: IUserService
  15. {
  16. /// <summary>
  17. /// 数据库上下文(注入)
  18. /// </summary>
  19. public PandaCMSDbContext PandaCMSDbContext { get; set; }
  20. /// <summary>
  21. /// 构造函数
  22. /// </summary>
  23. /// <param name="pandaCMSDbContext"></param>
  24. public UserService (PandaCMSDbContext pandaCMSDbContext)
  25. {
  26. this.PandaCMSDbContext = pandaCMSDbContext;
  27. }
  28. /// <summary>
  29. /// 增加用户
  30. /// </summary>
  31. /// <param name="user">用户模型</param>
  32. /// <returns></returns>
  33. public bool AddUser (UserModel user)
  34. {
  35. return true;
  36. }
  37. /// <summary>
  38. /// 删除用户
  39. /// </summary>
  40. /// <param name="userId">用户Id</param>
  41. /// <returns></returns>
  42. public bool DeleteUser (Guid userId)
  43. {
  44. return true;
  45. }
  46. /// <summary>
  47. /// 更新用户信息
  48. /// </summary>
  49. /// <param name="userId">用户Id</param>
  50. /// <param name="userModel">用户信息</param>
  51. /// <returns></returns>
  52. public bool UpdateUserInfo (Guid userId, UserModel userModel)
  53. {
  54. return true;
  55. }
  56. /// <summary>
  57. /// 重置密码
  58. /// </summary>
  59. /// <param name="userId">用户Id</param>
  60. /// <param name="oldHashedPassword">旧密码(已哈希)</param>
  61. /// <param name="newHashedPassword">新密码(已哈希)</param>
  62. /// <returns></returns>
  63. public bool ResetPassword (Guid userId, string oldHashedPassword, string newHashedPassword)
  64. {
  65. return true;
  66. }
  67. /// <summary>
  68. /// 获得所有用户
  69. /// </summary>
  70. /// <returns></returns>
  71. public IEnumerable<UserModel> GetAllUser ()
  72. {
  73. return new List<UserModel>();
  74. }
  75. /// <summary>
  76. /// 获得用户信息通过用户Id
  77. /// </summary>
  78. /// <param name="userId">用户Id</param>
  79. /// <returns></returns>
  80. public UserModel GetUserById (Guid userId)
  81. {
  82. return new UserModel();
  83. }
  84. /// <summary>
  85. /// 获得用户的所有API权限
  86. /// </summary>
  87. /// <param name="userId">用户Id</param>
  88. /// <returns></returns>
  89. public IEnumerable<ApiPermissionItemModel> GetUserAllApiPermission (Guid userId)
  90. {
  91. return new List<ApiPermissionItemModel>();
  92. }
  93. /// <summary>
  94. /// 获得用户的Id,通过用户的账号
  95. /// </summary>
  96. /// <param name="account">用户的账号</param>
  97. /// <returns></returns>
  98. public async Task<Guid> GetUserId (string account)
  99. {
  100. Guid userId = await this.PandaCMSDbContext.UserModels.Where(item => item.Account == account)
  101. .Select(item=>item.Id)
  102. .FirstOrDefaultAsync();
  103. return userId;
  104. }
  105. /// <summary>
  106. /// 验证用户的账号和密码
  107. /// </summary>
  108. /// <param name="account">账号</param>
  109. /// <param name="passwordHashed">密码(Hashed)</param>
  110. /// <returns></returns>
  111. public bool CheckUserLogin (string account, string passwordHashed)
  112. {
  113. bool isExists = this.PandaCMSDbContext.UserModels
  114. .Where(item => item.Account == account && item.PasswordHashed == passwordHashed)
  115. .Count() > 0;
  116. return isExists;
  117. }
  118. /// <summary>
  119. /// 检测用户是否有指定的API URL权限
  120. /// </summary>
  121. /// <param name="UserId">用户Id</param>
  122. /// <param name="apiUrl">API URL字符串</param>
  123. /// <returns></returns>
  124. public bool CheckUserHaveApiPermission (Guid UserId, string apiUrl)
  125. {
  126. var apiPermissionItemModels = this.PandaCMSDbContext.ApiPermissionItemModels.FromSqlRaw(
  127. $@ "SELECT [Id],[Url]
  128. FROM [ApiPermissionItemModels]
  129. WHERE [Id] IN (
  130. SELECT
  131. [ApiPermissionItemModelsId]
  132. FROM
  133. [ApiPermissionGroupModelApiPermissionItemModel]
  134. WHERE
  135. [ApiPermissionGroupModelsId] IN (
  136. SELECT
  137. [BelongApiPermissionGroupModelsId]
  138. FROM
  139. [ApiPermissionGroupModelRoleModel]
  140. WHERE
  141. [RoleModelsId] IN (
  142. SELECT [RolesId]
  143. FROM [RoleModelUserModel]
  144. WHERE [BelongUsersId] = '{@UserId}'
  145. )
  146. )
  147. )");
  148. var result = apiPermissionItemModels
  149. .Where(item => item.Url.ToLower() == apiUrl.Trim().ToLower())
  150. .Count() > 0;
  151. return result;
  152. }
  153. }
  154. }

创建 UsersController.cs 控制器文件

使用 [Authorize] 属性保护控制器操作,但 登录(Login) 和 注册(Register)方法除外,它们允许通过在每个操作方法上使用 [AllowAnonymous] 属性覆盖控制器上的 [Authorize] 属性来允许公共访问。


   
  1. using PandaCMS.Authorization;
  2. using Microsoft.AspNetCore.Mvc;
  3. using PandaCMS.ViewModels.UserManagement;
  4. using PandaCMS.ServiceInterface.UserManagement;
  5. using PandaCMS.ServiceInterface.AuthorizationManagement;
  6. namespace PandaCMS.Web.API.Controllers
  7. {
  8. [ApiController]
  9. [Authorize]
  10. [Route( "[controller]")]
  11. public class UserController : ControllerBase
  12. {
  13. /// <summary>
  14. /// 日志服务(注入)
  15. /// </summary>
  16. private readonly ILogger<UserController> _logger;
  17. /// <summary>
  18. /// 用户服务(注入)
  19. /// </summary>
  20. private IUserService UserService { get; set; }
  21. /// <summary>
  22. /// 权限认证服务(注入)
  23. /// </summary>
  24. private IJwtService JwtService { get; set; }
  25. /// <summary>
  26. /// 构造函数
  27. /// </summary>
  28. /// <param name="logger"></param>
  29. /// <param name="userService"></param>
  30. public UserController (ILogger<UserController> logger, IUserService userService, IJwtService jwtService)
  31. {
  32. //初始化
  33. _logger = logger;
  34. this.UserService = userService;
  35. this.JwtService = jwtService;
  36. }
  37. /// <summary>
  38. /// 用户登录
  39. /// </summary>
  40. /// <param name="loginRequestViewModel"></param>
  41. /// <returns></returns>
  42. [HttpPost( "Login")]
  43. [AllowAnonymous]
  44. public async Task<IActionResult> Login ([FromForm] LoginRequestViewModel loginRequestViewModel)
  45. {
  46. //验证用户名和密码
  47. bool checkResult = this.UserService.CheckUserLogin(loginRequestViewModel.Account, loginRequestViewModel.Password);
  48. if (checkResult)
  49. {
  50. //获得用户Id
  51. Guid userId = await this.UserService.GetUserId(loginRequestViewModel.Account);
  52. //生成Token
  53. string token = this.JwtService.GenerateToken(userId);
  54. return this.Ok(new LoginResonseViewModel()
  55. {
  56. JwtToken = token,
  57. Account = userId.ToString()
  58. });
  59. }
  60. else
  61. {
  62. return this.BadRequest(new
  63. {
  64. Msg = "用户名或密码有误。"
  65. });
  66. }
  67. }
  68. /// <summary>
  69. /// 退出登录
  70. /// </summary>
  71. /// <returns></returns>
  72. [HttpGet( "LoginOut")]
  73. public async Task<IActionResult> LoginOut ()
  74. {
  75. bool isSuccess = await this.JwtService.InvalidationUserTokenByServer(this.JwtService.GetToken());
  76. if(isSuccess)
  77. {
  78. return this.Ok(new
  79. {
  80. msg = "success",
  81. code = 666
  82. });
  83. }
  84. else
  85. {
  86. return this.BadRequest(new
  87. {
  88. msg = "error",
  89. code = 555
  90. });
  91. }
  92. }
  93. /// <summary>
  94. /// 更新Token
  95. /// </summary>
  96. /// <param name="updateTokenRequestViewModel"></param>
  97. /// <returns></returns>
  98. [HttpPost( "UpdateToken")]
  99. public async Task<IActionResult> UpdateToken ()
  100. {
  101. //先把旧的Token废除
  102. if(!await this.JwtService.InvalidationUserTokenByServer(this.JwtService.GetToken()))
  103. {
  104. return BadRequest(new UpdateTokenResponseViewModel()
  105. {
  106. Code = "555",
  107. Message = "Error:废除旧Token出错",
  108. NewToken = ""
  109. });
  110. }
  111. //生成新Token
  112. string newToken = this.JwtService.GenerateToken(this.JwtService.GetUserId());
  113. return Ok(new UpdateTokenResponseViewModel()
  114. {
  115. Code = "666",
  116. Message = "Success",
  117. NewToken = newToken
  118. });
  119. }
  120. }
  121. }

配置服务、中间件、配置文件

安装 system.identitymodel.tokens.jwt 包

dotnet add package system.identitymodel.tokens.jwt

完整的 Program.cs 文件


   
  1. //引入命名空间
  2. using Microsoft.IdentityModel.Tokens;
  3. using System.Text;
  4. using PandaCMS.Authorization;
  5. using Microsoft.AspNetCore.Authorization;
  6. using PandaCMS.Data.DbContexts;
  7. using Microsoft.EntityFrameworkCore;
  8. using Microsoft.EntityFrameworkCore.Design;
  9. using Microsoft.EntityFrameworkCore.SqlServer;
  10. using PandaCMS.Service.UserManagement;
  11. using PandaCMS.ServiceInterface.UserManagement;
  12. using PandaCMS.Service.AuthorizationManagement;
  13. using PandaCMS.ServiceInterface.AuthorizationManagement;
  14. var builder = WebApplication.CreateBuilder(args);
  15. // Add services to the container.
  16. //注册数据库上下文服务
  17. builder.Services.AddDbContext<PandaCMSDbContext>(options =>
  18. {
  19. //注册Sql Server
  20. options.UseSqlServer(builder.Configuration.GetConnectionString( "SqlServerConnectionStrings"));
  21. //注册MySQL
  22. //ToDo:MySQL的版本号,放到配置文件中
  23. //options.UseMySql(builder.Configuration.GetConnectionString("MySqlConnectionString"), new MySqlServerVersion(new Version(10, 4, 17)));
  24. //注册SQLite
  25. //options.UseSqlite(builder.Configuration.GetConnectionString("Sqlite"));
  26. });
  27. //注册自定义服务
  28. builder.Services.AddScoped<IJwtService, JwtService>();
  29. builder.Services.AddScoped<IUserService, UserService>();
  30. builder.Services.AddScoped<IRoleService, RoleService>();
  31. builder.Services.AddControllers();
  32. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
  33. builder.Services.AddEndpointsApiExplorer();
  34. builder.Services.AddSwaggerGen();
  35. var app = builder.Build();
  36. // Configure the HTTP request pipeline.
  37. if (app.Environment.IsDevelopment())
  38. {
  39. app.UseSwagger();
  40. app.UseSwaggerUI();
  41. }
  42. app.UseHttpsRedirection();
  43. //启用JWT中间件
  44. app.UseMiddleware<JwtMiddleware>();
  45. app.MapControllers();
  46. app.Run();

配置文件 appsettings.json


   
  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft.AspNetCore": "Warning"
  6. }
  7. },
  8. "AllowedHosts": "*",
  9. "ConnectionStrings": {
  10. "SqlServerConnectionStrings": "Server=127.0.0.1;Database=PandaCMS;User Id=sa;Password=1Q;",
  11. "MySqlConnectionString": "server=127.0.0.1;database=PandaCMS;uid=root;password=1Q;",
  12. "Sqlite": "Data Source=PandaCMS.db;"
  13. },
  14. "Authorization": {
  15. "JwtAuth": {
  16. "Key": "wwwpanda666comKey",
  17. "ValidateAudience": true,
  18. "ValidateIssuer": true,
  19. "Issuer": "panda666.com",
  20. "Audience": "panda666.com",
  21. "ExpiresDay": "7"
  22. },
  23. "DeniedAction": "/api/nopermission"
  24. }
  25. }

参考文档

https://jasonwatmore.com/post/2021/05/25/net-5-simple-api-for-authentication-registration-and-user-management


转载:https://blog.csdn.net/weixin_38304160/article/details/127782615
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场