官网

1、简介

JSON Web Token(JWT)是一个基于 RFC 7519 的开放数据标准,它定义了一种宽松且紧凑的数据组合方式,使用 JSON 对象在各应用之间传输信息(信息加密即 jwe,签名即 jws)。

2、使用场景
  • 授权: 简单说就是生成 token (使用较多)

    • 这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够轻松跨不同域使用。
  • 信息交换:使用公钥/私钥加密传输信息

    • JSON Web Tokens 是一种在各方之间安全传输信息的好方法。因为 JWT 可以被签名——例如,使用公钥/私钥对——你可以确定发件人就是他们所说的那样。此外,由于使用标头和有效负载计算签名,因此您还可以验证内容是否未被篡改。
3、jwt 优势
  • 简洁(Compact):可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  • 不需要在服务端保存会话信息,特别适用于分布式微服务。
4、JSON Web Token 结构

在这里插入图片描述

一个 JWT 通常有 HEADER (头),PAYLOAD (有效载荷)和 SIGNATURE (签名)三个部分组成,三者之间使用“.”链接,如下所示

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InpoYW5nc2FuIiwicGhvbmUiOiIxMjU4MSIsImRlcGFydCI6Im1hbmdlciJ9.
RPjj3YG6aJpLb7F0cy7O5xP94d5O3r7823kFu4GQe4w

jwt 组成详解如下

①、HEADER
{
  "alg": "HS256", # 加密算法
  "typ": "JWT"
}

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 = base64UrlEncode({
  "alg": "HS256",
  "typ": "JWT"
})
②、PAYLOAD

不建议存私密信息

{
  "sub": "1234567890",
  "name": "zhangsan",
  "phone": "12581",
  "depart": "manger"
}
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InpoYW5nc2FuIiwicGhvbmUiOiIxMjU4MSIsImRlcGFydCI6Im1hbmdlciJ9 = 
base64UrlEncode({
  "sub": "1234567890",
  "name": "zhangsan",
  "phone": "12581",
  "depart": "manger"
})
③、SIGNATURE
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  salt123456  # salt
) 

RPjj3YG6aJpLb7F0cy7O5xP94d5O3r7823kFu4GQe4w = 
HMACSHA256(
   base64UrlEncode({
  "alg": "HS256",
  "typ": "JWT"
})  + "." +
  base64UrlEncode({
  "sub": "1234567890",
  "name": "zhangsan",
  "phone": "12581",
  "depart": "manger"
}),
  salt123456  # salt
)

base64UrlEncode(header) +"." + base64UrlEncode(payload)+"." + HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),salt123456 ) = JWT

HEADER 和 PAYLOAD是通过 base64 编码的,没有加密,不安全不能存储敏感信息
在这里插入图片描述

5、测试 JWT

新建一个 maven 项目

①、导包
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.3.0</version>
    </dependency>
②、运行
    // 过期时间 3 秒钟,测试过期
    public static final long EXPIRE_TIME = 3 * 1000;

    /**
     * 校验token是否正确
     *
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            // 根据密码生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC512(secret);
            JWTVerifier verifier = JWT.require(algorithm).withClaim("userName", username).build();
            // 效验TOKEN
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 获得 token 中的信息无需 secret 解密也能获得
     *
     * @return token中包含的用户名
     */
    public static String getUserName(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("userName").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成签名,5min后过期
     *
     * @param username 用户名
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String secret) throws UnsupportedEncodingException {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC512(secret);
        // 附带userName信息
        return JWT.create().withHeader(new HashMap<>()).withClaim("userName", username).withExpiresAt(date).sign(algorithm);
    }

    public static void main(String[] args) throws UnsupportedEncodingException, InterruptedException {
        String userName = "admin";
        String secret = "123456";
        String token = sign(userName, secret);
        System.out.println(token);
        System.out.println(getUserName(token));
        Thread.sleep(5000);
        System.out.println(verify(token, userName, secret));
        try {
            // 会校验 token 是否过期,如果过期抛出异常
            System.out.println(JWT.require(Algorithm.HMAC512("123456")).build().verify(token).getClaim("userName").asString());
        } catch (JWTVerificationException e) {
            System.out.println("抛出异常");
        }
        
        // 请求头 HEADER 默认值
        System.out.println(JWT.decode(token).getHeaderClaim("typ").asString());
        System.out.println(JWT.decode(token).getHeaderClaim("alg").asString());
        // token 过期也可以获取到
        System.out.println(JWT.decode(token).getClaim("userName").asString());
    }

返回结果如下

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VyTmFtZSI6ImFkbWluIiwiZXhwIjoxNjY3ODM1MjM2fQ.fph0Z-NYIF2eYlDQ4XH1_f5cNU6tnqanYu9WtDusC_3FzCwlh2Z9SZG3sUGUmjcAf9n1jOZe1ODbpgBU0KA-2A
admin
false
抛出异常
JWT
HS512
admin

Process finished with exit code 0

如果 token 过期,会抛异常

6、总结

如上就是 JWT 的简单使用,在实际开发中

  • 后端(java):都是封装一个工具类 JwtUtil,在单体应用中可以在拦截器中判断 token 是否有效,分布式应用可以在网关中配置,如果实现单点登录或者部署集群模式,可以讲 token 存入 redis 中
  • 前端(vue):登陆成功可以将 token 存在localstore 中,在使用 axios.create({}).interceptors 添加拦截器,在请求头中添加 header,之后每个请求都携带 token
Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐