.NET CORE 6 集成认证机制JWT
一:什么是JWT?
JWT (全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
二:JWT数据结构
头部
JWT 第一部分是头部分,它是一个描述 JWT 元数据的 Json 对象
{ "alg": "HS256", "typ": "JWT" }
alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为HS256),typ 属性表示令牌的类型,JWT 令牌统一写为JWT。
最后,使用 Base64 URL 算法将上述 JSON 对象转换为字符串保存。
载荷
JWT 第二部分是 Payload,也是一个 Json 对象,除了包含需要传递的数据,还有七个默认的字段供选择。
-
iss (issuer):签发人/发行人
-
sub (subject):主题
-
aud (audience):用户
-
exp (expiration time):过期时间
-
nbf (Not Before):生效时间,在此之前是无效的
-
iat (Issued At):签发时间
-
jti (JWT ID):用于标识该 JWT
也可自定义字段
{ "sub": "1234567890", "name": "John Doe", "admin": true }
签名
JWT第三部分是签名(Signature)
签名部分是对前两部分(头部,载荷)的签名,防止数据篡改。
按下列步骤生成:
1、先指定密钥(secret)
2、把头部(header)和载荷(payload)信息分别base64转换
3、使用头部(header)指定的算法加密
最终,签名(signature) = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用 "点"( . )分隔
三:JWT特点
- JWT更加简洁
- JWT适合一次性验证
- JWT适合无状态认证
- 相对于数据库Session查询更加省时
- JWT默认不加密
JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的令牌的权限,一旦JWT签发,在有效期内将会一直有效
JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。
为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。
四:JWT的使用
项目引用包:Microsoft.AspNetCore.Authentication.JwtBearer
配置文件中添加
"JWTConfig": { "Secret": "abcdefghijklmnop", //密钥 "Issuer": "Sw163.API", //颁发者 "Audience": "Jst", //使用者 "AccessExpiration": 30, //过期时间 "RefreshExpiration": 60 //刷新过期时间 },
在Token中添加JwtHelper类
JwtHelper
using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; namespace SW163.Token.Model { /// <summary> /// 令牌 /// </summary> public class JwtUserInfo { /// <summary> /// Id /// </summary> public long Uid { get; set; } /// <summary> /// 角色 /// </summary> public string Role { get; set; } /// <summary> /// 职能 /// </summary> public string Work { get; set; } } /// <summary> /// JWT操作帮助类 /// </summary> public class JwtHelper { /// <summary> /// 颁发JWT /// </summary> /// <param name="tokenModel">当前颁发对象的用户信息</param> /// <returns>JWT字符串</returns> public static string IssueJwt(JwtUserInfo jwtUserInfo) { #region 【Step1-从配置文件中获取生成JWT所需要的数据】 string iss = Appsettings.GetVal(new string[] { "JWTConfig", "Issuer" });//颁发者 string aud = Appsettings.GetVal(new string[] { "JWTConfig", "Audience" });//使用者 string secret = Appsettings.GetVal(new string[] { "JWTConfig", "Secret" }); //密钥 #endregion #region 【Step2-通过Claim创建JWT中的Payload(载荷)信息】 var claimsIdentity = new List<Claim> { new Claim(JwtRegisteredClaimNames.Jti, jwtUserInfo.Uid.ToString()), //JWT ID new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),//JWT的发布时间 new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(600)).ToUnixTimeSeconds()}"),//JWT到期时间 new Claim(JwtRegisteredClaimNames.Iss,iss), //颁发者 new Claim(JwtRegisteredClaimNames.Aud,aud)//使用者 }; //添加用户的角色信息(非必须,可添加多个) var claimRoleList = jwtUserInfo.Role.Split(',').Select(role => new Claim(ClaimTypes.Role, role)).ToList(); claimsIdentity.AddRange(claimRoleList); #endregion #region 【Step3-签名对象】 var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); //创建密钥对象 var sigCreds = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); //创建密钥签名对象 #endregion #region 【Step5-将JWT相关信息封装成对象】 var jwt = new JwtSecurityToken( issuer: iss, claims: claimsIdentity, signingCredentials: sigCreds); #endregion #region 【Step6-将JWT信息对象生成字符串形式】 var jwtHandler = new JwtSecurityTokenHandler(); string token = jwtHandler.WriteToken(jwt); #endregion return token; } // END IssueJwt() /// <summary> /// 将JWT加密的字符串进行解析 /// </summary> /// <param name="jwtStr">JWT加密的字符</param> /// <returns>JWT中的用户信息</returns> public static JwtUserInfo SerializeJwtStr(string jwtStr) { JwtUserInfo jwtUserInfo = new JwtUserInfo(); var jwtHandler = new JwtSecurityTokenHandler(); if (!string.IsNullOrEmpty(jwtStr) && jwtHandler.CanReadToken(jwtStr)) { //将JWT字符读取到JWT对象 JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr); //获取JWT中的用户信息 jwtUserInfo.Uid = Convert.ToInt64(jwtToken.Id); object role; jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role); //获取角色信息 jwtUserInfo.Role = role == null ? "" : role.ToString(); } return jwtUserInfo; } //END SerializeJwt() } }
Appsettings.cs
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SW163.Token { /// <summary> /// 用于帮助读取appsettings.json中的系统配置参数 /// </summary> public class Appsettings { static IConfiguration Configuration { get; set; } public Appsettings(IConfiguration configuration) { Configuration = configuration; } /// <summary> /// 获取用appsettings.json某个字段下的值 /// </summary> /// <param name="sections">获取值所在的字段(基于JSON层次结构,某个值会存在于多个层级的字段中)</param> /// <returns>JSON字段的值</returns> public static string GetVal(params string[] sections) { try { if (sections.Any()) { string key = string.Join(":", sections); return Configuration[key]; } } catch (Exception) { } return string.Empty; } // END GetVal() /// <summary> /// 获取用appsettings.json某个字段下值(值是一个组数) /// </summary> /// <param name="sections">获取值所在的字段(基于JSON层次结构,某个值会存在于多个层级的字段中)</param> /// <returns>JSON字段的多个值(集合)</returns> public static List<T> GetValues<T>(params string[] sections) { List<T> list = new List<T>(); Configuration.Bind(string.Join(":", sections), list); return list; } // END GetValues() } }
Startup添加JWT认证的相关配置
ConfigureServices中添加
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "JST API", Version = "v1" }); #region var basePath = PlatformServices.Default.Application.ApplicationBasePath; var xmlPath = Path.Combine(basePath, "WebApi.xml"); c.IncludeXmlComments(xmlPath, true); #endregion #region Token绑定到ConfigureServices // 开启小锁 c.OperationFilter<AddResponseHeadersFilter>(); c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>(); // 在header中添加token,传递到后台 c.OperationFilter<SecurityRequirementsOperationFilter>(); c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", Name = "Authorization",//jwt默认的参数名称 In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中) Type = SecuritySchemeType.ApiKey }); #endregion }); services.AddSingleton(new Appsettings(Configuration)); //将Appsettings对象以单例模式进行注入
Configure中添加
//先开启认证 app.UseAuthentication(); //在开启授权 app.UseAuthorization();
创建授权控制器验证
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using SW163.Domain; using SW163.Domain.IRepository; using SW163.Token.Model; using SW163.WebApi.Controllers; using SW163.Infrastructure; using System.Threading.Tasks; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; using System.Text; using System.IdentityModel.Tokens.Jwt; using System; using System.ComponentModel; namespace SW163.WebApi.Api.System { /// <summary> /// 获取jwt令牌 /// </summary> [Route("api/[controller]")] [Description("账户信息")] [ApiController] public class AccountController : ControllerBase { public AccountController(IAdministratorRepository administratorRepository) { AdministratorRepository = administratorRepository; } public IAdministratorRepository AdministratorRepository { get; } [HttpGet,Route("GetJwtStr")] public async Task<object> GetJwtStr(string UserName, string PassWord) { //验证登录有效性。。。。 var pwd =DESEncrypt.Encrypt(PassWord); var admin = await AdministratorRepository.GetEntityAsync(x => x.UserName == UserName && x.PassWord == pwd); if (admin!=null) { //登陆成功后,基于当前用户生成JWT令牌字符串 JwtUserInfo jwtUserInfo = new JwtUserInfo { Uid = 1, Role = "Admin,Leader" }; string jwtStr = JwtHelper.IssueJwt(jwtUserInfo); return Ok(new { success = true, token = jwtStr, Id = admin.Id, RoleId = admin.RoleId }); } return NotFound(new { success = false, token = "此账号不存在", Id = 0, RoleId = 0 }); } } }
运行结果
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxIiwiaWF0IjoiMTY4NDc0NDQzNCIsImV4cCI6IjE2ODQ3NDUwMzQiLCJpc3MiOiJTdzE2My5BUEkiLCJhdWQiOiJKc3QiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiQWRtaW4iLCJMZWFkZXIiXX0.eaQKe4kOdx5p2iSQV_Td_bqmX-vOdh6gb0y7mxPmkXM