JWT使用教程

一,什么是 JW

JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准,定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

  • 简洁: 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库

二,JWT 组成部分

2.1,令牌组成

  • 标头:Header
  • 有效载荷:Payload
  • 签名:Singature

2.2,详细

标头
一个json字符串,包含当前令牌名称,以及加密算法,

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

typ用来标识整个token是一个jwt字符串,alg代表签名和摘要算法,一般签发JWT的时候,只要typ和alg就够了,生成方式是将header部分的json字符串经过Base64Url编码;
载荷
存放有效的信息,可以是标准声明,也可以是自定义的内容,可以在代码中获取;

  • 标准声明
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

标准声明,不是强制性的,你可以自定义内容,但是不要存放重要信息,如:用户密码等;
这一部分信息容易被获取。

签名
由头部信息使用base64加密之后,拼接上载荷使用base64加密之后的部分,在加上当前的密钥,进行头部中的加密算法进行加密

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

三,在Java中使用

在Java中使用JWT,有许多的第三方库,如:java-jwt,jjwt等;本文主要介绍这两种库;

3.1,java-jwt

官网https://github.com/auth0/java-jwt
1,导入依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.0.0</version>
</dependency>

2,代码实现

  • 生成JWT
void sign() {
        // 创建签名算法
        Algorithm algorithm = Algorithm.HMAC256("esyrg");
        // 标头
        Map<String, Object> headMap = new HashMap<>();
        headMap.put("type", "JWT");
        headMap.put("alg", "HMAC256");
        JWTCreator.Builder builder = JWT.create().withHeader(headMap);
        // 载荷
        JWTCreator.Builder builder1 = builder.withClaim("iss", "esyrg")
                .withClaim("sub", "user");
        // 过期时间,2分钟
        // 这里设置过期时间不能用Calendar生成Date,否则设置的过期时间无效
        long l = System.currentTimeMillis() + (1000 * 60 * 2);
        Date date = new Date(l);
        String jwt = builder1.withExpiresAt(date).sign(algorithm);
        System.out.println(jwt);
    }

注意:设置过期时间不能用Calendar生成Date,否则设置的过期时间无效,至于原因本人也不清楚。

  • 验证
void verifyJwt() {
        String jwt = "eyJ0eXAiOiJKV1QiLCJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJ1c2VyIiwiaXNzIjoiZXN5cmciLCJleHAiOjE2NjE1MjM5NTh9.FMl14s3GJ5n9sKrpMIYIqgSAQ-isWTTSdT6InUli1lQ";
        Algorithm algorithm = Algorithm.HMAC256("esyrg");
        DecodedJWT verify = JWT.require(algorithm).build().verify(jwt);
        // 获取标头
        String header = verify.getHeader();
        // 获取载荷或自定义信息
        Claim iss = verify.getClaim("iss");
        String string = iss.asString();
    }

常用方法:

  • decodeJwt(String token)
  • decode(String token)

以上这两种方法都可以解析获取jwt中的信息,但是这两种方法不会验证jwt的签名。

void verifyJwt() {
        String jwt = "eyJ0eXAiOiJKV1QiLCJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJ1c2VyIiwiaXNzIjoiZXN5cmcifQ.zLlDZzys7xEbMD6bwSEdTuscS5LGYEDCAyxlUj5AWx8";
        DecodedJWT decode = JWT.decode(jwt);
        Claim iss = decode.getClaim("iss");
    }

常见异常

令牌过期异常:TokenExpiredException
签名无效或key错误异常:SignatureVerificationException
算法不匹配异常:AlgorithmMismatchException

可用签名算法(摘自官网)

JWS算法描述
HS256HMAC256带有 SHA-256 的 HMAC
HS384HMAC384带有 SHA-384 的 HMAC
HS512HMAC512带有 SHA-512 的 HMAC
RS256RSA256带有 SHA-256 的 RSASSA-PKCS1-v1_5
RS384RSA384带有 SHA-384 的 RSASSA-PKCS1-v1_5
RS512RSA512带有 SHA-512 的 RSASSA-PKCS1-v1_5
ES256ECDSA256曲线 P-256 和 SHA-256 的 ECDSA
ES384ECDSA384具有曲线 P-384 和 SHA-384 的 ECDSA
ES512ECDSA512曲线 P-521 和 SHA-512 的 ECDSA

3.2,jjwt

官网https://github.com/jwtk/jjwt
导入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

代码实现

  • 生成JWT
void contextLoads() {
        // 标头
        Map<String, Object> headMap = new HashMap<>();
        headMap.put("type", "JWT");
        headMap.put("alg", "HS256");
        // 自定义信息
        Map<String, Object> cliMap = new HashMap<>();
        cliMap.put("id", "1");
        cliMap.put("name", "ren");
        // 过期时间
        long l = System.currentTimeMillis() + (1000 * 60 * 2);
        Date date = new Date(l);
        JwtBuilder jwtBuilder = Jwts.builder().setHeader(headMap)
                .setClaims(cliMap)
                .setExpiration(date);

        // 签名,这里有两种方法
        // 第一种,此方法要求key长度必须足够长,否则会报错
//        SecretKey secretKey = Keys.hmacShaKeyFor("esyrgfdfdfdf".getBytes());
//        String jwt = jwtBuilder.signWith(secretKey,SignatureAlgorithm.HS256).compact();
        // 第二种,建议采用此法方法
//        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String jwt = jwtBuilder.signWith(secretKey).compact();
        System.out.println(jwt);
    }
  • 解析JWT
void verifyJwt() {
        String jwt = "eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJuYW1lIjoicmVuIiwiaWQiOiIxIn0.6LxD4SdCPVYbbijePBGQu2oIRTa23ZsI-wHMzhYfIx8";
        // 这里的secretKey必须和生成时用的secretKey一样
        Jwt parse = Jwts.parserBuilder().setSigningKey(secretKey).build().parse(jwt);
        Claims body = (Claims) parse.getBody();
        Object name = body.get("name");
        Header header = parse.getHeader();
        Object type = header.get("type");

        // 还可以以下面这种方式解析jwt
        // Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(jwt);
        // Claims body = claimsJws.getBody();
        // JwsHeader header = claimsJws.getHeader();
    }

常见异常

令牌过期异常:ExpiredJwtException
签名或jwt错误:SignatureException

四,JWT工具类

以下为JWT工具类,使用的依赖是java-jwt。

/**
 * JWT工具类
 * <p>
 * 使用的是java-jwt
 *
 * @Author: Esyrg
 * @Date: 2022/8/27 17:57
 */
public class JWTUtils {

    /**
     * 生成签名
     *
     * @param claims     附带信息
     * @param secret     生成token的密钥
     * @param expireTime 过期时间,单位:秒
     * @return 加密的token
     */
    public static String sign(Map<String, String> claims, String secret, Long expireTime) {
        Date date = new Date(System.currentTimeMillis() + expireTime * 1000);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        // 附带信息
        JWTCreator.Builder builder = JWT.create();
        for (String key : claims.keySet()) {
            builder.withClaim(key, claims.get(key));
        }
        // 过期时间
        builder.withExpiresAt(date);
        return builder.sign(algorithm);
    }

    /**
     * 验证token
     *
     * @param token  要验证的token
     * @param secret 密钥
     * @return 是否正确
     */
    public static boolean verify(String token, String secret) {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier build = JWT.require(algorithm).build();
        build.verify(token);
        return true;
    }

    /**
     * 验证token
     *
     * @param token  要验证的token
     * @param secret 密钥
     * @return 状态及信息,1:正确,0:错误
     */
    public static Map<String, Object> verifyInfo(String token, String secret) {
        Map<String, Object> map = new HashMap<>();
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier build = JWT.require(algorithm).build();
            build.verify(token);
            map.put("state", "1");
            map.put("message", "令牌正确");
            return map;
        } catch (TokenExpiredException e) {
            // 需要打印时,可以去掉注释
            // e.printStackTrace();
            map.put("state", "0");
            map.put("message", "令牌过期");
            return map;
        } catch (SignatureVerificationException e) {
            // e.printStackTrace();
            map.put("state", "0");
            map.put("message", "验证错误");
            return map;
        } catch (AlgorithmMismatchException e) {
            // e.printStackTrace();
            map.put("state", "0");
            map.put("message", "算法不匹配");
            return map;
        } catch (Exception e) {
            // e.printStackTrace();
            map.put("state", "0");
            map.put("message", e.getMessage());
            return map;
        }
    }

    /**
     * 获取token中的信息,无需解密也能获得
     *
     * @param token 要解析的token
     * @param key   信息的key
     * @return 信息
     */
    public static String getClaims(String token, String key) {
        try {
            DecodedJWT decode = JWT.decode(token);
            return decode.getClaim(key).asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 获取过期时间,无需解密也能获得
     *
     * @param token 需要解析的token
     * @return 过期时间
     */
    public static Date getExpiresAt(String token) {
        try {
            DecodedJWT decode = JWT.decode(token);
            return decode.getExpiresAt();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 获取token的剩余时间,无需解密也能获得,单位:毫秒
     *
     * @param token 需要解析的token
     * @return 剩余时间,如果已过期,则返回 -1
     */
    public static Long getExpiresAtMill(String token) {
        try {
            DecodedJWT decode = JWT.decode(token);
            long l = decode.getExpiresAt().getTime() - System.currentTimeMillis();
            return l < 0 ? -1L : l;
        } catch (JWTDecodeException e) {
            return null;
        }
    }
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值