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-iohttps://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.htmlhttps://blog.51cto.com/u_9806927/12294431https://blog.csdn.net/qq_19636353/article/details/126983153https://www.cnblogs.com/gongchengship/p/18488204

posted @   tuqunfu  阅读(294)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示