首页 > 其他分享 >JWT

JWT

时间:2024-04-04 20:44:23浏览次数:13  
标签:String JWT claims Claims ID Constants

1.概念

JSON Web Tokens,一种紧凑的Claims声明格式,旨在用于空间受限的环境进行传输,常见的场景如HTTP授权请求头参数和URI查询参数。
JWT会把Claims转换成JSON格式,而这个JSON内容将会应用为JWS结构的有效载荷或者应用为JWE结构的(加密处理后的)原始字符串,通过消息认证码(Message Authentication Code或者简称MAC)和/或者加密操作对Claims进行数字签名或者完整性保护。

  • JWE:JSON Web Encryption,表示基于JSON数据结构的加密内容,加密机制对任意八位字节序列进行加密、提供完整性保护和提高破解难度
  • JWS:JSON Web Signature,表示使用JSON数据结构和BASE64URL编码表示经过数字签名或消息认证码(MAC)认证的内容,数字签名或者MAC能够提供完整性保护
  • JWA:JSON Web Algorithm,JSON Web算法,数字签名或者MAC算法

2.实现方式

  • 基于JWE实现
    依赖于加解密算法、BASE64URL编码和身份认证等手段提高传输的Claims的被破解难度
  • 基于JWS的实现
    使用了BASE64URL编码和数字签名的方式对传输的Claims提供了完整性保护,也就是仅仅保证传输的Claims内容不被篡改,但是会暴露明文。
    目前主流的JWT框架中大部分都通过JWS的实现

3.Claims

JWT的核心作用就是保护Claims的完整性(或者数据加密),保证JWT传输的过程中Claims不被篡改(或者不被破解)。
Claims在JWT原始内容中是一个JSON格式的字符串,其中单个Claim是K-V结构,作为JsonNode中的一个field-value,这里列出常用的规范中预定义好的Claim:
| iss | Issuer | 发行方 |
| sub | Subject | 主体 |
| aud | Audience | (接收)目标方 |
| exp | Expiration Time | 过期时间 |
| nbf | Not Before | 早于该定义的时间的JWT不能被接受处理 |
| iat | Issued At | JWT发行时的时间戳 |
| jti | JWT ID | JWT的唯一标识 |
Claim并不要求强制使用,何时选用何种Claim完全由使用者决定,而为了使JWT更加紧凑,这些Claim都使用了简短的命名方式去定义。
在不和内建的Claim冲突的前提下,使用者可以自定义新的公共Claim

4.Header

在JWT规范文件中称这些Header为JOSE Header,JOSE的全称为Javascript Object Signature Encryption,也就是Javascript对象签名和加密框架,JOSE Header其实就是Javascript对象签名和加密的头部参数。

5.应用场景

JWT的使用场景和实战#
JWT本质是一个令牌,更多场景下是作为会话ID(session_id)使用,作用是'维持会话的粘性'和携带认证信息(如果用JWT术语,应该是安全地传递Claims)。笔者记得很久以前使用的一种Session ID解决方案是由服务端生成和持久化Session ID,返回的Session ID需要写入用户的Cookie,然后用户每次请求必须携带Cookie,Session ID会映射用户的一些认证信息,这一切都是由服务端管理,一个很常见的例子就是Tomcat容器中出现的J(ava)SESSIONID。与之前的方案不同,JWT是一种无状态的令牌,它并不需要由服务端保存,携带的数据或者会话的数据都不需要持久化,使用JWT只需要关注Claims的完整性和合法性即可,生成JWT时候所有有效数据已经通过编码存储在JWT字符串中。正因JWT是无状态的,一旦颁发后得到JWT的客户端都可以通过它与服务端交互,JWT一旦泄露有可能造成严重安全问题,因此实践的时候一般需要做几点:

JWT需要设置有效期,也就是exp这个Claim必须启用和校验
JWT需要建立黑名单,一般使用jti这个Claim即可,技术上可以使用布隆过滤器加数据库的组合(数量少的情况下简单操作甚至可以用Redis的SET数据类型)
JWS的签名算法尽可能使用安全性高的算法,如RSXXX
Claims尽可能不要写入敏感信息
高风险场景如支付操作等不能仅仅依赖JWT认证,需要进行短信、指纹等二次认证
JWT一般用于认证场景,搭配API网关使用效果甚佳。多数情况下,API网关会存在一些通用不需要认证的接口,其他则是需要认证JWT合法性并且提取JWT中的消息载荷内容进行调用,针对这个场景:

对于控制器入口可以提供一个自定义注解标识特定接口需要进行JWT认证,这个场景在Spring Cloud Gateway中需要自定义实现一个JWT认证的WebFilter
对于单纯的路由和转发可以提供一个URI白名单集合,命中白名单则不需要进行JWT认证,这个场景在Spring Cloud Gateway中需要自定义实现一个JWT认证的GlobalFilter

主流的JWT方案是JWS,此方案是只编码和签名,不加密,务必注意这一点,JWS方案是无状态并且不安全的,关键操作应该做多重认证,也要做好黑名单机制防止JWT泄漏后造成安全性问题。

6.基于jjwt实现

  • 依赖
<!--jwt-->
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.0</version>
</dependency>
<!--token生成-->
<dependency>
  <groupId>javax.xml.bind</groupId>
  <artifactId>jaxb-api</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-core</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>javax.activation</groupId>
  <artifactId>activation</artifactId>
  <version>1.1.1</version>
</dependency>
  • token生成和解析工具类
@Component
@Slf4j
public class TokenProvider {
    // 签名算法
    private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS512;
    // 对称密钥生成
    private SecretKeySpec signingKey = null;
    // 私钥
    private PrivateKey privateKey = null;
    // 公钥
    private PublicKey publicKey = null;

    @PostConstruct
    public void init() {

    }

    public TokenResponse createToken(JwtAccessToken jwtAccessToken, long TTLMills, Map<String, Object> params) throws Exception {
        // 获取公钥和私钥
        try {
            publicKey = KeyUtils.getPublicKey(Constants.RS512_PUBLICKEY);
            privateKey = KeyUtils.getPrivateKey(Constants.RS512_PRIVATEKEY);
        } catch (Exception e) {
            log.error("获取PublicKey,PrivateKey失败", e.getMessage());
        }

        TokenResponse tokenResponse = new TokenResponse();
        Map<String, Object> claims = new HashMap<>();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        claims.put(Claims.ID, "1.0");
        claims.put(Claims.ISSUER, jwtAccessToken.getIss());
        claims.put(Claims.SUBJECT, "LWX-Auth-Manager");
        claims.put(Claims.AUDIENCE, jwtAccessToken.getSub());
        if (params != null) {
            Iterator<String> it = params.keySet().iterator();
            while (it.hasNext()) {
                String key = it.next();
                claims.put(key, params.get(key));
            }
        }
        // 生成token
        JwtBuilder jwtBuilder = Jwts.builder().setClaims(claims);
        if (TTLMills >= 0) {
            long expMills = nowMillis + TTLMills;
            Date exp = new Date(expMills);
            jwtBuilder.setExpiration(exp).setNotBefore(now);
            tokenResponse.setExpTime(DateFormatUtils.format(exp, "yyyy-MM-dd HH:mm:ss"));
        }
        String value = signatureAlgorithm.getValue();
        //加密方式兼容
        String type1 = "HS512";
        String type2 = "RS512";
        if (type1.equals(value)) {
            jwtBuilder.signWith(signatureAlgorithm, signingKey);
        }
        if (type2.equals(value)){
            jwtBuilder.signWith(signatureAlgorithm, privateKey);
        }
        String token = jwtBuilder.compact();
        tokenResponse.setAccessToken(token);
        tokenResponse.setRenewal(TTLMills);
        return tokenResponse;
    }

    public Claims parseToken(String jsonWebToken) throws RuntimeException {
        Claims claims = null;
        try {
            if(privateKey == null){
                privateKey = KeyUtils.getPrivateKey(Constants.RS512_PRIVATEKEY);
            }
            claims = Jwts.parser().setAllowedClockSkewSeconds(60).setSigningKey(privateKey).parseClaimsJws(jsonWebToken).getBody();
        } catch (ExpiredJwtException ex) {
            log.error("登录凭证已经过期", ex);
        } catch (Exception ex) {
            log.error("登录凭证不合法", ex);
        }
        return claims;
    }
}
  • 使用的工具类和实体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtAccessToken {

    private String iss;

    private String iat;

    private Long exp;
    
    private String aud;
    
    private String sub;
}
public class KeyUtils {
    public static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = Base64.getDecoder().decode(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = Base64.getDecoder().decode(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TokenResponse {
    private String accessToken;

    private String expTime;

    private long renewal = 0L;
}
  • 测试类
public void getJwt() throws Exception {
    JwtAccessToken jwtAccessToken = new JwtAccessToken();
    jwtAccessToken.setIss("Lwx");
    Map<String, Object> params = new HashMap<>();
    params.put(Constants.LOGIN_USER_ID, "123");
    params.put(Constants.LOGIN_ACCOUNT_ID, "321");
    params.put(Constants.LOGIN_USER_NAME, "lwx");
    params.put(Constants.LOGIN_LOGINNAME,"lwxlogin" );
    params.put(Constants.LOGIN_ACCOUNT_NAME, "lwxacc");
    params.put(Constants.LOGIN_APP_ID, "1");
    long expiredTime = Constants.DEFAULT_TOKEN_EXPIRED_TIME;
    
    TokenResponse tokenResponse = null;
    tokenResponse = tokenProvider.createToken(jwtAccessToken, expiredTime, params);
    
    System.out.println(tokenResponse.getAccessToken());


    // 解析token获取claims 储存基本信息
    Claims claims = tokenProvider.parseToken(tokenResponse.getAccessToken());
    if (claims != null) {
        String userId = claims.get(Constants.LOGIN_USER_ID).toString();
        String accountId = claims.get(Constants.LOGIN_ACCOUNT_ID).toString();
        String loginName = claims.get(Constants.LOGIN_ACCOUNT_NAME).toString();
        String appId = claims.get(Constants.LOGIN_APP_ID).toString();
        String userName = "";

        System.out.println(loginName);
    }
}

7.问题

JWT应用于认证场景,算法上使用了安全性稍高的RS256,使用RSA算法进行签名生成。项目上线初期,JWT的过期时间都固定设置为7天,生产日志发现该API网关周期性发生"假死"现象,具体表现为:

Nginx自检周期性出现自检接口调用超时,提示部分或者全部API网关节点宕机
API网关所在机器的CPU周期性飙高,在用户访问量低的时候表现平稳
通过ELK进行日志排查,发现故障出现时段有JWT集中性过期和重新生成的日志痕迹
排查结果表明JWT集中过期和重新生成时候使用RSA算法进行签名是CPU密集型操作,同时重新生成大量JWT会导致服务所在机器的CPU超负载工作。初步的解决方案是:

JWT生成的时候,过期时间添加一个随机数,例如360000(1小时的毫秒数) ~ 8640000(24小时的毫秒数)之间取一个随机值添加到当前时间戳加7天得到exp值
这个方法,对于一些老用户营销场景(老用户长时间没有登录,他们客户端缓存的JWT一般都已经过期)没有效果。有时候运营会通过营销活动唤醒老用户,大量老用户重新登录有可能出现爆发性大批量重新生成JWT的情况,对于这个场景提出两个解决思路:

首次生成JWT时候,考虑延长过期时间,但是时间越长,风险越大
提升API网关所在机器的硬件配置,特别是CPU配置,现在很多云厂商都有弹性扩容方案,可以很好应对这类突发流量场景

标签:String,JWT,claims,Claims,ID,Constants
From: https://www.cnblogs.com/lwx11111/p/18114577

相关文章

  • jwt实现登录 和 接口实现动态权限
     [Authorize]  ====  usingMicrosoft.AspNetCore.Authorization;   登录的DTOnamespacelogin;publicclassWeatherForecast{publicDateOnlyDate{get;set;}publicintTemperatureC{get;set;}publicintTemperatureF=>32+(in......
  • C# .NET6 WebAPI JWT身份验证服务
    自定义扩展类usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Authentication.JwtBearer;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.AspNetCore.Mvc.ModelBinding;usingSystem.Text.Json;namespaceDemo{///<summary>///......
  • C# 生成JWT
    usingMicrosoft.IdentityModel.Tokens;usingSystem.IdentityModel.Tokens.Jwt;usingSystem.Security.Claims;usingSystem.Text;namespaceDemo{///<summary>///Token处理类///</summary>publicclassToken{///<......
  • 云原生最佳实践系列 6:MSE 云原生网关使用 JWT 进行认证鉴权
    方案概述MSE网关可以为后端服务提供转发路由能力,在此基础上,一些敏感的后端服务需要特定认证授权的用户才能够访问。MSE云原生网关致力于提供给云上用户体系化的安全解决方案,其中JWT认证能力是在JsonWebToken这种结构化令牌的基础上实现了一套基于用户体系对用户的API(服......
  • token、jwt 和 jwt刷新token
     概念涉及到身份验证和授权的机制。Token概念:在网络通信中,Token是一个用于身份验证和授权的令牌。它通常是一个字符串,由服务端生成并发送给客户端,客户端在后续的请求中携带该令牌以证明自己的身份。Token可以是任意形式的字符串,比如随机生成的字符串、加密后的......
  • JWT示例与原理
    简介JWT(JSONWebToken)是一种去中心化的web认证方案,信息存储在客户端。数据结构JWT通常由3部分组成,Header、Payload、Signature。每个部分都是用Base64Url编码后的字符串,每个部分之间由点分割。形如Header.Payload.Signature注:Base64Url是Base64的一个变种,主要是将Base6......
  • 使用egg.js发送jwt
    下载jwt    配置jwtpnpmiegg-jwt plugin.js/**@typeEgg.EggPlugin*/module.exports={jwt:{enable:true,package:'egg-jwt'}};config.default.jsconfig.jwt={secret:"hakurei77"//密钥}创建中间层    app->......
  • jwt编码
    JWT(jsonwebtoken)JSONWebToken(JWT)是一个开放标准(RFC7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。 这边流量包的cookie中存在jwt编码header:payload: ......
  • thinkphp6中jwt的使用
    thinkphp6中jwt的使用安装JWT插件composerrequirefirebase/php-jwt创建User模型phpthinkmake:modelUser创建User控制器phpthinkmake:controllerUser封装创建token函数,要在User模型中创建//加密的秘钥protected$key="test";//过期时间protect......
  • 基于security-oauth2-autoconfigure实现的OAuth2迁移到更现代的解决方案,Spring Securi
    目录OAuth2资源服务器配置步骤1:添加依赖步骤2:配置资源服务器OAuth2客户端配置(可选)/**其他应用作为OAuth2客户端步骤1:添加依赖步骤2:配置OAuth2.0客户端/**应用同时作为OAuth2客户端步骤1:配置OAuth2.0客户端控制器示例结合使用OAuth2与JWT        ......