buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

JWT令牌/JwtToken

JWT代表JSON Web Token,它是一种用于在网络应用之间安全传输信息的开放标准(RFC 7519)。JWT令牌由三个部分组成,以点号(.)分隔:

  • 头部(Header):包含令牌的类型(即"JWT")和所使用的签名算法(例如HMAC、RSA或ECDSA)。
  • 有效载荷(Payload):也称为声明(Claims),包含有关令牌的信息,如用户身份、权限和其他元数据。
  • 签名(Signature):使用密钥对头部和有效载荷进行签名,以验证令牌的完整性和真实性。

下面示例是使用相同key生成的2个JWT令牌串,

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDY3OTAxMjUsInVzZXJuYW1lIjoic29uZ2h1YSJ9.2SXhF6siH_VHg39e4Nr_XH3bfBJ_IQDs7zYqbuW27Tg
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzYWxlTmFtZSI6IuW8oOS4iSIsImV4cCI6MTcwNjc5MDEyNiwidXNlcm5hbWUiOiJ6aGFuZ3NhbiJ9.rOlJHe91_5tMkOkYs6otcBNanzyskK6oF8z1N8oAlNA

在这两个示例token串中

  • 第一部分Header是相同的{"typ": "JWT", "alg": "HS256"},
  • 第二部分payload是数据部分,是对原始键值对(一个或多个,可以反序列化为map或json对象)做base64编码得到的字符串。既然是base64编码,那么,payload串的长度与原json串的长度正相关,原json串越长(键值对越多),base64串越长。
  • 第三部分是利用HS256算法和key对payload原串生成的数字签名(定长)。

 

通过JWT在线解码工具可以查看jwtoken中的header和payload。其中,payload里包含的一个名为exp的second时间戳(Unix时间戳(Unix timestamp)转换工具),表示的是这个token的过期时间。

 

 

JWT令牌具有以下特点:

  • 自包含性:有效载荷中包含了令牌的所有必要信息,避免了服务器端存储会话信息的需求。
  • 可扩展性:有效载荷可以包含自定义声明,使其适应各种用例和应用需求。
  • 安全性:由于签名的存在,JWT令牌在传输过程中是可验证的,防止内容篡改和伪造。

JWT令牌通常用于身份验证和授权,在客户端和服务器之间进行安全的无状态通信。当用户进行身份验证后,服务器会生成JWT令牌并将其发送给客户端,客户端随后将其在后续请求中作为授权凭证发送给服务器。服务器验证JWT令牌的有效性和完整性,并使用其中的信息进行授权和身份验证。

由于JWT令牌是基于标准的开放协议,因此在不同的环境中可以广泛应用,例如Web应用程序、移动应用程序和API等。

 

 

JWT令牌如何保证安全性和防止篡改和伪造?

JWT令牌通过使用签名机制来保证其安全性,以防止篡改和伪造。以下是JWT令牌如何实现安全性的工作原理:

  1. 生成签名密钥:JWT令牌的签名是使用秘密密钥生成的。该密钥只在服务器端知道,用于验证令牌的真实性。

  2. 创建令牌:当服务器生成JWT令牌时,它将头部和有效载荷进行Base64编码,并使用签名密钥进行签名。生成的签名将作为令牌的第三部分。

  3. 传输令牌:服务器将JWT令牌发送给客户端,并客户端在后续请求中将其作为授权凭证发送回服务器。

  4. 验证令牌:服务器在接收到JWT令牌后,首先检查令牌的完整性。它将再次使用相同的签名密钥对接收到的头部和有效载荷进行签名,并将生成的签名与令牌中的签名进行比较。如果两者匹配,那么令牌是完整的且未被篡改的。

  5. 提取数据:如果令牌的签名验证通过,服务器可以提取有效载荷中的数据,并使用其中的信息进行授权和身份验证。

通过这个过程,JWT令牌可以保证其安全性和防止篡改和伪造。任何试图篡改有效载荷或修改签名的尝试都会被服务器检测到,因为签名无法匹配。同时,由于签名密钥只在服务器端存在,客户端无法生成有效的签名,从而确保了令牌的真实性。

重要的是要保护签名密钥的安全性。如果签名密钥泄露,攻击者可能会伪造有效的签名并篡改令牌。因此,密钥管理和保护是实现JWT令牌安全性的关键。

 

 

JwtUtil -生成Token / 验证Token / 获取payload中的claim

package jstudy.jwt;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Assert;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author Scott
 * @Date 2018-07-12 14:23
 * @Desc JWT工具类
 **/
@Slf4j
public class JwtUtil {

    /**
     * 扩展token
     *
     * @param payloadMap
     * @param secret
     * @return
     */
    public static String createToken(Map<String, String> payloadMap, String secret, Long ttlMillis) {
        JWTCreator.Builder builder = JWT.create();
        payloadMap.forEach(builder::withClaim);

        Date expiresAt = new Date(System.currentTimeMillis() + ttlMillis); // Token过期时间
        Algorithm algorithm = Algorithm.HMAC256(secret);
        return builder.withExpiresAt(expiresAt).sign(algorithm);
    }

    /**
     * 验证token有效及是否过期
     *
     * @param token
     * @param secret
     * @return
     */
    public static boolean verify(String token, String secret) {
        // 根据密码生成JWT效验器
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm).build();

        try {
            // 校验TOKEN   : 优先校验签名,然后校验是否过期
            DecodedJWT decodeJWT = verifier.verify(token);

//            //验证token是否过期
//            Date expiresAt = decodeJWT.getExpiresAt();
//            if (expiresAt.before(new Date())) {
//                log.warn("token校验失败,token已过期 -{}", token);
//                return false;
//            }

            return true;
        }
        // token验签失败异常-SignatureVerificationException:The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256
        catch (SignatureVerificationException exception) {
            log.warn("token校验失败 -{}", exception.getMessage());
            return false;
        }
        // token过期异常-TokenExpiredException:The Token has expired on Fri Feb 23 19:26:52 CST 2024.
        catch (TokenExpiredException e) {
            log.warn("token校验失败-已过期 -{}", e.getMessage());
            return false;
        }
        // 如果入参token不是一个有效的JWTtoken,会抛出JWT解码异常 JWTDecodeException: The token was expected to have 3 parts,
        catch (JWTDecodeException e) {  but got 1.
            log.warn("token校验失败-不是有效token -{}", e.getMessage());
            throw new UserAuthException("token非法");

        }
    }

    /**
     * 从token的payload里获取信息(无需secret解密也能获得)
     *
     * @return
     */
    public static String getClaim(String token, String keyField) {
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getClaim(keyField).asString();
    }

    /**
     * 直接利用base64解码可以解析出来JwtToken的header和payload
     *
     * @param token
     */
    private static void decodeToken(String token) {
        String[] array = token.split("\\.");
        System.out.println("解析token的header-->" + Base64.decodeStr(array[0]));
        System.out.println("解析token的payload-->" + Base64.decodeStr(array[1]));

        // 也可以这样解析
        DecodedJWT decodedJWT = JWT.decode(token);
        System.out.println("解析token的header-->" + Base64.decodeStr(decodedJWT.getHeader()));
        System.out.println("解析token的payload-->" + Base64.decodeStr(decodedJWT.getPayload()));
    }



    public static void main(String[] args) throws InterruptedException {
        String key = "username";

        Map<String, String> stringStringMap = new HashMap<>();
        stringStringMap.put(key, "songhua");
        stringStringMap.put("loginAccount", "a@b.com");
        String token = createToken(stringStringMap, "secret15210572383", TimeUnit.MILLISECONDS.toMillis(30));
        System.out.println("token-->" + token);
//        decodeToken(token);


        boolean verify1 = verify(token, "secret15210572383--");
        Assert.isFalse(verify1, "应是false");

        // 测试token过期
        Thread.sleep(1000);
        boolean verify2 = verify(token, "secret15210572383");
        Assert.isFalse(verify2, "应是false");


        String username = getClaim(token, key);
        System.out.println("token中存储的username=" + username);

    }
}

 

 

本文设计物料:https://www.processon.com/view/link/637c88efe401fd60d4cfddb0

posted on 2019-06-10 17:21  buguge  阅读(2645)  评论(0编辑  收藏  举报