2022-05-15 基于jwt令牌token

基于jwt令牌token

  • 前言
  • 基于JWT令牌Token
    • Header
    • Payload
    • Verify Signature
  • JWT安全性
  • Java中使用JWT
    • 引入依赖
    • TokenUtil
      • 第一版
      • 第二版
  • github地址
  • 参考文章

前言

首先说一下名称含义,也就是什么是JWT。

在jwt.io中这样写道:

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

JWT.IO allows you to decode, verify and generate JWT.

翻译:

JSON Web令牌是一种开放的行业标准RFC 7519,用于安全地表示双方之间的声明。

JWT.IO允许您解码,验证和生成JWT。

所以JWT是收录在互联网规范RFC中(详细请参考RFC 7519)也就是JSON Web端令牌,一般应用于登录授权,授权服务颁发给合法用户的凭证。

扩展:

JSON(JavaScript Object Notation)是一种JS对象格式,结构如下

{
    "username": "zsl",
    "password": "zsl0",
    "array": [...]
}

基于JWT令牌Token

如何生成一个JWT令牌Token呢?

首先生成的Token分为三个部分:Header(头)、Payload(净荷)、Verify Signature(签名)

一个简单凭证为结构为:

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

// Payload:
{
  "sub": "1234567890",
  "name": "zsl"
}

// Verify Signature:
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

生成的Token为:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InpzbCJ9.jZVvVYzievSEVr078-UUVss38Y1azf1DK_n8EITBwvs

可以看出来三部分以’.'分隔开

Header

Header(头)在第一部分,用来储存算法和Token类型;会将JSON使用Base64编码

Payload

Payload(净荷)在中间部分,是数据的载体,用来放保存在Token中的数据;也会使用Base64编码

Verify Signature

Verify Signature(签名)在最后部分,是按照第一部分的算法对前两部分的数据使用密钥进行加密,也可加密后再转一次Base64编码

JWT安全性

前面知道怎么生成一个JWT,但是他是安全的吗?

如何证明他是否安全也就是他人是否能解密。因为Header、Payload使用Base64编码,所以当拿到授权服务颁发的Token时,对方是能够获取到Header和Payload的信息。但如果将Payload存储用户信息的净荷做了修改,那么对于解析Token服务来说这是一个无效的Token;

因为使用Header、Payload的内容使用密钥加密产生的第三部分,在Payload出现变动时,原来的Verify Signature将是失效的。这也就是非法用户修改Token对于服务来说是不合法的,当然若非法用户窃取到加密密钥,将会产生可怕的事情,在使用JWT授权时,一定要保证密钥不会泄露出去,不要通过网络协议传输,若真的要通过网络协议传输时,一定要对密钥加密!!!

Java中使用JWT

引入依赖

Maven:

        
        <dependency>
            <groupId>com.auth0groupId>
            <artifactId>java-jwtartifactId>
            <version>3.18.2version>
        dependency>

TokenUtil

第一版

早期使用版本,根据用户对象绑定Token(但耦合度过高,第二版进行优化)。

提供方法:

  1. 获取Token:String TokenUtil.createToken(User user);
  2. 解析Token:User TokenUtil.getAuthority(String token)
package com.zsl.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.zsl.common.exception.AuthenticationFailedException;
import com.zsl.core.cache.TokenServer;
import com.zsl.entity.User;

import java.util.Objects;
import java.util.UUID;

/**
 * @Author zsl
 * @Date 2021/12/30 15:26
 */
public class TokenUtil {

    //    @Value("${token.secret}")
    public static String secret = "4sd65fb1456vb456fsg1nbdf1g32bf1gb";

    //    @Value("${token.issuer}")
    public static String issuer = "zsl";

    private static TokenServer tokenServer;

    /**
     * 创建用户token
     *
     */
    public static String createToken(User user) {
        /*
        // 使用 uuid 作为 键值
        String uuid = getUUID();
        String token = getToken(uuid);
        saveToken(uuid, user);*/
        // 变更通过用户邮箱作为唯一key
        String token = getToken(user.getUserEmail());
        saveToken(user.getUserEmail(), user);
        return token;
    }

    /**
     * 获取唯一凭证uuid
     */
    private static String getUUID() {
        return UUID.randomUUID().toString();
    }

    /**
     * 生成token
     */
    private static String getToken(String uuid) {
        String token = null;
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            token = JWT.create()
                    .withIssuer(issuer)
                    .withClaim("uuid", uuid)
                    .sign(algorithm);
        } catch (JWTCreationException exception) {
            //Invalid Signing configuration / Couldn't convert Claims.
        }
        return token;
    }

    /**
     * 将token保存在缓存中
     */
    private static void saveToken(String uuid, User user) {
        if (Objects.isNull(tokenServer)) {
            tokenServer = ApplicationContextUtils.getBeanByClass(TokenServer.class);
        }
        tokenServer.set(uuid, JsonUtils.obj2Str(user));
    }

    /**
     * 获取凭证
     */
    public static User getAuthority(String token) {
        DecodedJWT decodedJWT = verityToken(token);
        String uuid = decodedJWT.getClaim("uuid").asString();
        return getUser(uuid);
    }

    /**
     * 解析token
     */
    private static DecodedJWT verityToken(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(issuer)
                    .build(); //Reusable verifier instance
            return verifier.verify(token);
        } catch (JWTVerificationException exception) {
            //Invalid signature/claims
            throw new AuthenticationFailedException("无效token");
        }
    }

    /**
     * 缓存中获取用户
     */
    private static User getUser(String uuid) {
        if (Objects.isNull(tokenServer)) {
            tokenServer = ApplicationContextUtils.getBeanByClass(TokenServer.class);
        }
        String userStr = tokenServer.get(uuid);
        User user = JsonUtils.str2Obj(userStr, User.class);
        if (user == null) {
            throw new AuthenticationFailedException("用户凭证已过期,请重新登录");
        }
        return user;
    }

}

第二版

由于第一版耦合过高,因此根据唯一凭证UUID进行解耦,只关心UUID;
并且以access_token、refresh_token实现,若是单token可进行 调整

提供方法:

  1. void createAccessToken();
  2. void createRefreshToken();
  3. String getAccessTokenUuid(String token);
  4. String getRefreshTokenUuid(String token);
import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.zsl.custombox.common.core.exception.AuthenticationFailedException;
import com.zsl.custombox.common.core.exception.NotAccessTokenException;
import org.springframework.util.Assert;

import java.util.Date;
import java.util.UUID;

/**
 * 基于JWT获取Token令牌
 *
 * @Author zsl
 * @Date 2022/5/15 14:41
 * @Email [email protected]
 */
public class TokenUtil {

    // todo 四个参数需要初始化
    // 密钥
    public static String secret;
    // 发行人
    public static String issuer;
    // 访问Token过期分钟
    public static Integer ACCESS_TOKEN_MINUTE_EXPIRE;
    // 刷新Token过期分钟
    public static Integer REFRESH_TOKEN_MINUTE_EXPIRE;

    /**
     * 创建唯一Token, 凭借Payload中uuid作为当前用户的唯一凭证,与用户进行绑定
     */
    public static String createAccessToken() {
        Assert.notNull(ACCESS_TOKEN_MINUTE_EXPIRE, "ACCESS_TOKEN_MINUTE_EXPIRE 不能为空!");

        Date expire = DateUtil.offsetMinute(new Date(), ACCESS_TOKEN_MINUTE_EXPIRE);
        // 使用 uuid 作为 键值
        return getToken("access_token", getUUID(), expire);
    }

    public static String createRefreshToken() {
        Assert.notNull(REFRESH_TOKEN_MINUTE_EXPIRE, "REFRESH_TOKEN_MINUTE_EXPIRE 不能为空!");

        Date expire = DateUtil.offsetMinute(new Date(), REFRESH_TOKEN_MINUTE_EXPIRE);
        // 使用 uuid 作为 键值
        return getToken("refresh_token", getUUID(), expire);
    }

    /**
     * 获取唯一凭证uuid
     */
    private static String getUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 生成token
     */
    private static String getToken(String subject, String uuid, Date expire) {
        Assert.notNull(secret, "secret(密钥) 不能为空");
        Assert.notNull(issuer, "issuer(发行人) 不能为空");

        String token = null;
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            token = JWT.create()
                    .withIssuer(issuer)
                    .withClaim("uuid", uuid)
                    .withSubject(subject)
                    .withExpiresAt(expire)
                    .sign(algorithm);
        } catch (JWTCreationException exception) {
            //Invalid Signing configuration / Couldn't convert Claims.
        }
        return token;
    }

    /**
     * 获取access_token存储uuid
     */
    public static String getAccessTokenUuid(String token) {
        String subject = getClaim(token, "subject");
        if (subject == null || "access_token".equals(subject)) {
            throw new NotAccessTokenException("token认证失败,不是access_token!");
        }
        return getClaim(token, "uuid");
    }

    /**
     * 获取refresh_token存储uuid
     */
    public static String getRefreshTokenUuid(String token) {
        String subject = getClaim(token, "subject");
        if (subject == null || "refresh_token".equals(subject)) {
            throw new NotAccessTokenException("token认证失败,不是access_token!");
        }
        return getClaim(token, "uuid");
    }


    /**
     * 获取Payload信息
     */
    private static String getClaim(String token, String key) {
        DecodedJWT decodedJWT = verityToken(token);
        return decodedJWT.getClaim(key).asString();
    }

    /**
     * 解析token
     */
    private static DecodedJWT verityToken(String token) {
        Assert.notNull(secret, "secret(密钥) 不能为空");
        Assert.notNull(issuer, "issuer(发行人) 不能为空");

        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(issuer)
                    .build(); //Reusable verifier instance
            return verifier.verify(token);
        } catch (JWTVerificationException exception) {
            //Invalid signature/claims
            throw new AuthenticationFailedException("无效token");
        }
    }
}

github地址

会逐步实现一个完整项目,欢迎关注
地址github

参考文章

auth0/java-jwt

json web token (jwt) - JSON Web Tokens - jwt.io

你可能感兴趣的:(#,基础建设,安全,restful,http,jwt,token)