ASP.NET Core之身份认证JWT
一、前言
上一章节介绍了通过Cookie和Session的方式进行身份的认证和授权,本章节介绍基于JWT的身份认证和授权,在使用JWT前先全面的了解关于JWT,从①定义、②原理、③使用方式、④使用场景、⑤特点、⑥优缺点几个方面进行描述。在全面了解的基础上,在ASP.NET Core中实现JWT的身份认证和授权的Demo。
二、JWT
定义,从jwt.io官网页中JWT是JSON Web Token的缩写(JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties),它是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。
原理,JWT是在服务器认证通过之后(比如账号密码验证)会生成一个JSON对象回传给用户,后续用户在于服务端通信时都需要回传该JSON对象,服务器完全依靠这个JSON对象确定用户身份,为了防止用户篡改数据,服务器在生成这个对象时候,会加上签名信息。因为这个JSON对象不会保存任何Session数据,所以服务器是完全无状态的,从而比较容易实现扩展。
{ "姓名": "xxxx", "头像": "xxxx", "角色": "管理员", "到期时间": "2018年7月1日0点0分" }
数据结构,JWT由三部分构成依次是Header(头部)、Payload(负载)、Signature(签名)。JWT token的格式是Header.Payload.Signature(头部.负载.签名),在jwt.io官网上的示例token信息如下
yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
cThIIoDvwdueQB468K5xDc5633seEFoqwxjF_xSJyQQ
上述的token通过.区分三部分内容,其中头部和负载都是JSON对象,对象包含相关信息项。
Header 部分是一个 JSON 对象,描述 JWT 的元数据,其中alg是描述签名的算法方式,默认使用HMAC SHA256(写成 HS256),官网中提供HS/RS/ES等各种类型的签名算法。typ是这个令牌的token类型,统一是JWT。最后,将上面的 JSON 对象使用 Base64URL 算法转换成字符串(yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9),这个是Header部分。
{ "alg": "HS256", "typ": "JWT" }
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用,当然除了官方给定的字段,用户可以自定义私有字段内容,比如过期时间、用户名、用户所拥有的权限。数据声明(Claims)分为注册声明(Registered claims)下述规定的官方字段、公共声明(Public claims)如用户信息、私有声明(Private claims)仅用于双方之间的信息交换,例如特定应用上下文下的自定义信息。负载是实际的用户数据以及其他自定义数据比如用户名,用户角色,过期时间等,注意不要存放私密数据密码等内容,因为JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
{ "exp": 2572682831, "user_name": "admin", "authorities": [ "admin" ], "jti": "c1a0645a-28b5-4468-b4c7-xxxxxxxx", "client_id": "client01", "scope": [ "all" ] }
Signature 部分是对前两部分的签名,防止数据篡改,由Header和Payload生成的签名信息,一旦Header和Payload被篡改,则签名验证失败。签名规则是将头部和负载进行Base64,然后加入一个密钥(secret),密钥(secret)只有生成的服务器知道,必须保密,不能泄露给用户,最后使用头部声明的编码类型进行编码,就得到了签名,如下Signature签名产生的公式。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
最后,JWT把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.
)分隔,就可以返回给用户,这个就是JWT的token信息,在网站中可以对token进行解析获取Header和Payload的JSON对象信息。
JWT使用方式,客户端接收到token可以保存在Cookie或者localStorage,客户端与服务器每次通信必须带上JWT。把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
Authorization: Bearer <token>
JWT的验证内容,①完整性检查,服务器通过重新计算签名并与收到的JWT的签名进行比较,确保JWT没有被篡改。它会使用服务器端的密钥来验证;②过期检查,服务器检查JWT的 exp
(过期时间)字段,确保令牌未过期。如果令牌过期,服务器将拒绝请求并要求用户重新认证;③权限验证,服务器检查JWT中的声明是否包含用户有权访问的资源。如果用户的权限不足,服务器会拒绝请求。
JWT的特点,①默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次;②JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数;③JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑;④JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证;⑤为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。总结无状态性、可扩展性、跨越认证、简洁和易于传输。
JWT的缺点,①续签问题,传统的 cookie+session 的方案天然的支持续签,但是 jwt 由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入 redis,虽然可以解决问题,但是 jwt 也变得不伦不类了。②注销问题(不可撤销),上述特点③,要实现注销可通过修改服务端的secret信息,从而让未过期的token验证失败,达到注销的目的。③密码重置问题,如果用户登录密码重置,此时的token还是生效的,必须通过修改secret的方式,实现token失效。
JWT的使用场景,①用户身份验证,常见的场景是单点登录(SSO)系统中,JWT 用于在多个系统之间共享用户身份验证状态;②OAuth授权,JWT 被用作访问令牌来授权用户访问API资源;③微服务架构中,在微服务架构中,JWT 可以在不同的微服务之间传递用户身份信息,避免每个微服务都需要独立进行身份验证。
JWT与自定义token的比较,token必须通过查库验证是否存在或者失效;JWT则不用查库或者少查库,直接在服务端进行校验,因为用户的信息及加密信息在第二部分payload和第三部分签证中已经生成,只要在服务端进行校验就行,并且校验也是JWT自己实现的。token则一般结合缓存数据Redis。
三、实践
在ASP.NET Core框架中使用JWT,主要包括注册JWT服务,生成JWT的Token信息,然后把Token传输至客户端,客户端在Header的Authorization传输至服务端进行验证。
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using System.Text; namespace tqf.LoginTokenRedis.demo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); // 添加Bearer认证 c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Scheme = "Bearer" }); // 为API添加Bearer认证需求 c.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }, Scheme = "oauth2", Name = "Bearer", In = ParameterLocation.Header, }, new List<string>() } }); }); // JWT builder.Services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(option =>{ option.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"])), ValidateIssuer = false, ValidateAudience = false, }; }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); // 可以在这里配置Swagger UI的其他选项,比如OAuth2客户端配置 }); } app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace tqf.LoginTokenRedis.demo.Controllers { [ApiController] [Route("[controller]")] public class LoginController : ControllerBase { private IConfiguration _configuration; public LoginController(IConfiguration configuration) { _configuration = configuration; } /// <summary> /// 登录 /// </summary> /// <param name="username"></param> /// <param name="password"></param> /// <returns></returns> [HttpPost(Name = "Login")] public ActionResult Login(string username, string password) { var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, username), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // 更多Claims }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: null, audience: null, claims: claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) }); } } }
四、总结
JWT(JSON Web Token)是一种基于JSON的轻量级令牌,用于在客户端和服务器之间安全传递信息。通过它的三部分结构(头部、载荷和签名),JWT确保数据的完整性和安全性。在现代Web开发中,JWT广泛用于身份验证和授权,特别是在微服务和跨域应用场景中。
参考:https://jwt.io/#debugger-io、https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html、https://blog.51cto.com/u_9806927/12294431、https://blog.csdn.net/qq_19636353/article/details/126983153、https://www.cnblogs.com/gongchengship/p/18488204
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步