随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

JWT实际上就是一个字符串,它由三部分组成:头部、载荷与签名。JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。适用于多客户端的前后端解决方案,JWT 是无状态化的,更适用于 RESTful 风格的接口验证。本文主要介绍使用JWT进行接口身份认证。

一、jwt介绍

1. JWT的结构体

JWT由三部分组成,分别是头信息、有效载荷、签名,中间以(.)分隔

(1)header(头信息) 描述 JWT 的元数据的JSON对象

由两部分组成,令牌类型(即:JWT)、散列算法(HMAC、RSASSA、RSASSA-PSS等)

  1. {"alg":"HS256","typ":"JWT"}

(2)Payload(有效载荷)

JWT的第二部分是payload,其中包含claims。claims是关于实体(常用的是用户信息)和其他数据的声明,claims有三种类型: registered, public, and private claims。

一个用来存放实际需要传递的数据的JSON 对象。如:

  1. }
  2. "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "admin",
  3. "exp": 1610877510,
  4. "iss": "cba",
  5. "aud": "cba"
  6. }

(3)Signature

要创建签名部分,必须采用编码的Header,编码的Payload,秘钥,Header中指定的算法,并对其进行签名。 对前两部分(Header、Payload)的签名,防止数据篡改。

  1. HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

2. JWT示例:

  1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE2MTA4Nzc1MTAsImlzcyI6ImNiYSIsImF1ZCI6ImNiYSJ9.O9lbZwfqRuA6vKcRCfYieA1zLkTPppdSvTc8UzwCkNw

二、准备工作

1. 添加NuGet程序包

Microsoft.AspNetCore.Authentication.JwtBearer

.Net Core--JWT身份验证 - 图1

(2).appsettings.json

  1. "JwtConfig": {
  2. "SecretKey": "123123123123", // 密钥 可以是guid 也可以是随便一个字符串
  3. "Issuer": "zhangsan", // 颁发者
  4. "Audience": "zhangsan", // 接收者
  5. "Expired": 30 // 过期时间(30min)
  6. },

2. 创建JWT配置类

  1. using Microsoft.Extensions.Options;
  2. using Microsoft.IdentityModel.Tokens;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. /// <summary>
  9. /// jwt配置
  10. /// </summary>
  11. public class JwtConfig : IOptions<JwtConfig>
  12. {
  13. public JwtConfig Value => this;
  14. public string SecretKey { get; set; }
  15. public string Issuer { get; set; }
  16. public string Audience { get; set; }
  17. public int Expired { get; set; }
  18. public DateTime NotBefore => DateTime.UtcNow;
  19. public DateTime IssuedAt => DateTime.UtcNow;
  20. public DateTime Expiration => IssuedAt.AddMinutes(Expired);
  21. private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
  22. public SigningCredentials SigningCredentials =>
  23. new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);
  24. }

3. 创建JWT工具类

  1. public class GenerateJwt
  2. {
  3. private readonly JwtConfig _jwtConfig;
  4. public GenerateJwt(IOptions<JwtConfig> jwtConfig)
  5. {
  6. _jwtConfig = jwtConfig.Value;
  7. }
  8. /// <summary>
  9. /// 生成token
  10. /// </summary>
  11. /// <param name="sub"></param>
  12. /// <param name="customClaims">携带的用户信息</param>
  13. /// <returns></returns>
  14. public JwtTokenResult GenerateEncodedTokenAsync(string sub, LoginUserModel customClaims)
  15. {
  16. //创建用户身份标识,可按需要添加更多信息
  17. var claims = new List<Claim>
  18. {
  19. new Claim("userid", customClaims.userid),
  20. new Claim("username", customClaims.username),
  21. //new Claim("realname",customClaims.realname),
  22. //new Claim("roles", string.Join(";",customClaims.roles)),
  23. //new Claim("permissions", string.Join(";",customClaims.permissions)),
  24. //new Claim("normalPermissions", string.Join(";",customClaims.normalPermissions)),
  25. new Claim(JwtRegisteredClaimNames.Sub, sub),
  26. };
  27. //创建令牌
  28. var jwt = new JwtSecurityToken(
  29. issuer: _jwtConfig.Issuer,
  30. audience: _jwtConfig.Audience,
  31. claims: claims,
  32. notBefore: _jwtConfig.NotBefore,
  33. expires: _jwtConfig.Expiration,
  34. signingCredentials: _jwtConfig.SigningCredentials);
  35. string access_token = new JwtSecurityTokenHandler().WriteToken(jwt);
  36. return new JwtTokenResult()
  37. {
  38. access_token = access_token,
  39. expires_in = _jwtConfig.Expired * 60,
  40. token_type = JwtBearerDefaults.AuthenticationScheme,
  41. user = customClaims
  42. };
  43. }
  44. }

4. JwtTokenResult

登录成功生成jwt返回给前端的model,LoginUserModel是用户信息model,我这里携带是为了避免前端解析token。

  1. /// <summary>
  2. /// 登录成功返回model
  3. /// </summary>
  4. public class JwtTokenResult
  5. {
  6. public string access_token { get; set; }
  7. public string refresh_token { get; set; }
  8. /// <summary>
  9. /// 过期时间(单位秒)
  10. /// </summary>
  11. public int expires_in { get; set; }
  12. public string token_type { get; set; }
  13. public LoginUserModel user { get; set; }
  14. }
  15. public class LoginUserModel
  16. {
  17. public string userid { get; set; }
  18. public string username { get; set; }
  19. public string realname { get; set; }
  20. public string roles { get; set; }
  21. public string permissions { get; set; }
  22. public string normalPermissions { get; set; }
  23. }

5. Startup配置

(1) .ConfigureServices注入jwt

  1. //注入jwt
  2. services.AddScoped<GenerateJwt>();
  3. services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));
  4. #region jwt验证
  5. var jwtConfig = new JwtConfig();
  6. Configuration.Bind("JwtConfig", jwtConfig);
  7. services.AddAuthentication(option =>
  8. {
  9. //认证middleware配置
  10. option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
  11. option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
  12. })
  13. .AddJwtBearer(options =>
  14. {
  15. options.TokenValidationParameters = new TokenValidationParameters
  16. {
  17. //Token颁发机构
  18. ValidIssuer = jwtConfig.Issuer,
  19. //颁发给谁
  20. ValidAudience = jwtConfig.Audience,
  21. //这里的key要进行加密
  22. IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecretKey)),
  23. //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
  24. ValidateLifetime = true,
  25. };
  26. });
  27. #endregion

(2).Configure

  1. app.UseAuthentication();//要在授权之前认证

.Net Core--JWT身份验证 - 图2

6. 调用获取token

  1. using JwtDemo.Model;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Threading.Tasks;
  8. namespace JwtDemo.Controllers
  9. {
  10. [Route("api/[controller]/[action]")]
  11. [ApiController]
  12. public class TestController : Controller
  13. {
  14. private readonly GenerateJwt _generateJwt;
  15. public TestController(GenerateJwt generate) {
  16. this._generateJwt = generate;
  17. }
  18. [HttpGet]
  19. public ActionResult GetLogin(string name,string userid) {
  20. var claims = new LoginUserModel()
  21. {
  22. userid = userid,
  23. username = name,
  24. realname = name,
  25. //roles = string.Join(";", user.roles),
  26. //permissions = string.Join(";", user.permissions),
  27. };
  28. var refreshToken = Guid.NewGuid().ToString();
  29. //当然 你要在生成token之前要验证一下账户是否在数据库存在 存在则生成
  30. /*
  31. 数据库查询
  32. */
  33. var jwtTokenResult = _generateJwt.GenerateEncodedTokenAsync(userid, claims);
  34. jwtTokenResult.refresh_token = refreshToken;
  35. return Json(jwtTokenResult);//这里可按需返回 如果不想返回用户信息 比如密码 可以在_generateJwt.GenerateEncodedTokenAsync去掉哦
  36. }
  37. }
  38. }

swagger调试

.Net Core--JWT身份验证 - 图3

postman请求

.Net Core--JWT身份验证 - 图4

7. 在后台中有时候我们需要获取当前用户的一些属性

JwtUser,用户解析jwt 获取当前用户信息

  1. public static class JwtUser
  2. {
  3. /// <summary>
  4. /// 解析jwt 获取当前用户信息
  5. /// </summary>
  6. /// <param name="request"></param>
  7. /// <returns>用户信息</returns>
  8. public static LoginUserModel GetRequestUser(this HttpRequest request)
  9. {
  10. var authorization = request.Headers["Authorization"].ToString();
  11. var auth = authorization.Split(" ")[1];
  12. var jwtArr = auth.Split('.');
  13. var dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
  14. //解析Claims
  15. var reqUser = new LoginUserModel
  16. {
  17. userid = dic["userid"],
  18. username = dic["username"],
  19. realname = dic["realname"],
  20. roles = dic["roles"],
  21. permissions = dic["permissions"],
  22. normalPermissions = dic["normalPermissions"],
  23. };
  24. return reqUser;
  25. }
  26. }

控制器中这样获取

  1. [HttpGet]
  2. public ActionResult GetUser() {
  3. var aa= JwtUser.GetRequestUser(this.Request);
  4. return Json(aa.userid+"===="+ aa.username); //获取到当前用户的姓名 角色等等就可以做一些业务处理
  5. }

8. 使用

在接口上标记身份验证

[AllowAnonymous] 标记此标识的接口是不需要验证的

[Authorize] 标记此标识的接口需要验证

  1. [Authorize]
  2. [HttpGet]
  3. public ActionResult GetDataList(string 参数)
  4. {
  5. /*
  6. 业务代码
  7. */
  8. return Json("");
  9. }

也可以直接标记在控制器上

获取token的接口最好单独写在一个控制器

.Net Core--JWT身份验证 - 图5

9. Swagger UI添加认证

在项目中通常都添加了Swagger UI来展示接口及基础测试,那么如果添加了认证后,如何在调用接口前添加认证信息呢? 

  1. #region 启用swagger验证功能
  2. //添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称一致即可。
  3. c.AddSecurityRequirement(new OpenApiSecurityRequirement
  4. {
  5. {
  6. new OpenApiSecurityScheme
  7. {
  8. Reference = new OpenApiReference {
  9. Type = ReferenceType.SecurityScheme,
  10. Id = "Bearer"
  11. }
  12. },
  13. new string[] { }
  14. }
  15. });
  16. c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
  17. {
  18. Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格",
  19. Name = "Authorization",//jwt默认的参数名称
  20. In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
  21. Type = SecuritySchemeType.ApiKey,
  22. BearerFormat = "JWT",
  23. Scheme = "Bearer",
  24. });
  25. #endregion

添加token即可

输入时 要输入Bearer +token 注意Bearer后有空格

.Net Core--JWT身份验证 - 图6

.Net Core--JWT身份验证 - 图7

其他方案

https://www.cnblogs.com/indexlang/p/indexlang.html

https://www.cnblogs.com/uoyo/p/13209685.html