首先说一下名称含义,也就是什么是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呢?
首先生成的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(头)在第一部分,用来储存算法和Token类型;会将JSON使用Base64编码
Payload(净荷)在中间部分,是数据的载体,用来放保存在Token中的数据;也会使用Base64编码
Verify Signature(签名)在最后部分,是按照第一部分的算法对前两部分的数据使用密钥进行加密,也可加密后再转一次Base64编码
前面知道怎么生成一个JWT,但是他是安全的吗?
如何证明他是否安全也就是他人是否能解密。因为Header、Payload使用Base64编码,所以当拿到授权服务颁发的Token时,对方是能够获取到Header和Payload的信息。但如果将Payload存储用户信息的净荷做了修改,那么对于解析Token服务来说这是一个无效的Token;
因为使用Header、Payload的内容使用密钥加密产生的第三部分,在Payload出现变动时,原来的Verify Signature将是失效的。这也就是非法用户修改Token对于服务来说是不合法的,当然若非法用户窃取到加密密钥,将会产生可怕的事情,在使用JWT授权时,一定要保证密钥不会泄露出去,不要通过网络协议传输,若真的要通过网络协议传输时,一定要对密钥加密!!!
Maven:
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.18.2version>
dependency>
早期使用版本,根据用户对象绑定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可进行 调整
提供方法:
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
auth0/java-jwt
json web token (jwt) - JSON Web Tokens - jwt.io