实现步骤
- 引入JWT包
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.3</version>
</dependency>
- 定义令牌类型枚举
package com.angel.ocean.token.constant;
public enum TokenTypeEnum {
ACCESS_TOKEN(0),
REFRESH_TOKEN(1);
private int code;
TokenTypeEnum(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
- 定义令牌数据模型
令牌信息类 TokenInfo
package com.angel.ocean.token.model;
import lombok.Data;
@Data
public class TokenInfo {
/**
* 令牌
*/
private String accessToken;
/**
* 令牌过期时间,秒级时间戳
*/
private Long accessTokenExpireIn;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 刷新令牌过期时间,秒级时间戳
*/
private Long refreshTokenExpireIn;
}
用户信息类 UserInfo
package com.angel.ocean.token.model;
import lombok.Data;
/**
* 用户信息
*/
@Data
public class UserInfo {
private Long uid;
private String name;
}
- 定义生成令牌的工具类
主要包含以下功能:
- 生成令牌
- 刷新令牌
- 获取令牌用户数据
- 令牌过期校验
package com.angel.ocean.token;
import com.angel.ocean.token.constant.TokenTypeEnum;
import com.angel.ocean.token.model.TokenInfo;
import com.angel.ocean.token.model.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class TokenUtil {
/**
* 过期时间(单位:秒)
*/
public static final Integer ACCESS_TOKEN_EXPIRE = 300;
/**
* 过期时间(单位:秒)
*/
public static final Integer REFRESH_TOKEN_EXPIRE = 7200;
/**
* 加密算法
*/
private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;
/**
* 私钥
*/
private final static String SECRET = "lMN0So4pxpeyPec3ap32Vg7e6JkjEjfT";
/**
* 秘钥实例
*/
public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());
/**
* jwt签发者
*/
private final static String JWT_ISS = "Ocean";
/**
* jwt主题
*/
private final static String SUBJECT = "Peripherals";
/**
* 生成令牌
* @param userInfo
* @return
*/
public static TokenInfo generateToken(UserInfo userInfo) {
Map<String, Object> payload = new HashMap<>();
payload.put("name", userInfo.getName());
payload.put("uid", userInfo.getUid());
payload.put("type", TokenTypeEnum.ACCESS_TOKEN.getCode());
Integer accessTokenExpireIn = ACCESS_TOKEN_EXPIRE;
String accessToken = generateToken(payload, accessTokenExpireIn);
payload.put("type", TokenTypeEnum.REFRESH_TOKEN.getCode());
Integer refreshTokenExpireIn = REFRESH_TOKEN_EXPIRE;
String refreshToken = generateToken(payload, refreshTokenExpireIn);
TokenInfo tokenInfo = new TokenInfo();
tokenInfo.setAccessToken(accessToken);
tokenInfo.setAccessTokenExpireIn(getTokenExpireIn(accessToken));
tokenInfo.setRefreshToken(refreshToken);
tokenInfo.setRefreshTokenExpireIn(getTokenExpireIn(refreshToken));
return tokenInfo;
}
/**
* 刷新令牌
* @param refreshToken
* @return
*/
public static TokenInfo refreshToken(String refreshToken) {
UserInfo userInfo = getUserInfoByToken(refreshToken);
return generateToken(userInfo);
}
/**
* 获取令牌用户数据
* @param token
* @return
*/
public static UserInfo getUserInfoByToken(String token) {
final Claims claims = parsePayload(token);
UserInfo userInfo = null;
if (null != claims) {
userInfo = new UserInfo();
userInfo.setName(claims.get("name").toString());
userInfo.setUid(Long.parseLong(claims.get("uid").toString()));
}
return userInfo;
}
/**
* 令牌过期校验 true-过期 false-未过期
* @param token
* @return
*/
public static Boolean isExpired(String token) {
Boolean result = true;
final Claims claims = parsePayload(token);
if (null != claims) {
String exp = claims.get("exp").toString();
long diff = Long.parseLong(exp) - System.currentTimeMillis() / 1000;
if(diff > 0) {
result = false;
}
}
return result;
}
/**
* 获取令牌的过期时间
*/
private static Long getTokenExpireIn(String token) {
Long expireIn = System.currentTimeMillis() / 1000;
final Claims claims = parsePayload(token);
if (null != claims) {
String exp = claims.get("exp").toString();
return Long.parseLong(exp);
}
return expireIn;
}
/**
* 生成令牌
*/
private static String generateToken(Map<String, Object> payload, Integer expireTime) {
Date expireDate = Date.from(Instant.now().plusSeconds(expireTime));
return Jwts.builder()
.header().add("typ", "JWT").add("alg", "HS256")
.and()
.claims(payload)
.id(UUID.randomUUID().toString())
.expiration(expireDate)
.issuedAt(new Date())
.subject(SUBJECT)
.issuer(JWT_ISS)
.signWith(KEY, ALGORITHM)
.compact();
}
/**
* 解析令牌claims
*/
private static Jws<Claims> parseClaim(String token) {
Jws<Claims> claimsJws = null;
try {
claimsJws = Jwts.parser() .verifyWith(KEY) .build().parseSignedClaims(token);
} catch (JwtException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return claimsJws;
}
/**
* 解析令牌header
*/
private static JwsHeader parseHeader(String token) {
Jws<Claims> claimsJws = parseClaim(token);
if(null == claimsJws) {
return null;
}
return claimsJws.getHeader();
}
/**
* 解析令牌payload
*/
private static Claims parsePayload(String token) {
Jws<Claims> claimsJws = parseClaim(token);
if(null == claimsJws) {
return null;
}
return claimsJws.getPayload();
}
}
生成令牌样式
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsIm5hbWUiOiJKYWltZSIsInR5cGUiOjAsImp0aSI6IjNjOWFhMDIxLWNkN2MtNDQ1Ni1hMTE1LTYxOWJkOTg3NzI2NCIsImV4cCI6MTcxOTA2MTQwMiwiaWF0IjoxNzE5MDYxMTAyLCJzdWIiOiJQZXJpcGhlcmFscyIsImlzcyI6Ik9jZWFuIn0.Cj_UJfbaWaiSu82ma-C1hd8L1u2_x3RRBHkXK5XFAPA",
"accessTokenExpireIn": 1719061402,
"refreshToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsIm5hbWUiOiJKYWltZSIsInR5cGUiOjEsImp0aSI6IjU4YTVmZDAzLTlkYWYtNDY2ZS05NjMwLTQyNjU5ODZmYTViNiIsImV4cCI6MTcxOTA2ODMwMywiaWF0IjoxNzE5MDYxMTAzLCJzdWIiOiJQZXJpcGhlcmFscyIsImlzcyI6Ik9jZWFuIn0.jegxFBj4t-AJhiWY_OByEs0vtsHBb0d0vjEerVFl85E",
"refreshTokenExpireIn": 1719068303
}
系统认证流程
- 用户登录系统,用户有效,生成令牌返回给客户端(Web前端或APP等);
- 客户端记录令牌信息和刷新令牌信息;
- 客户端携带令牌(一般放在头信息中)去请求服务器的资源;
- 服务进行令牌校验,检验通过返回资源信息给客户端,检验失败返回401(鉴权失败)给客户端;
- 客户端收到401响应码,知道令牌失效了,此时使用刷新令牌去重新获取令牌;
- 服务校验刷新令牌是否有效,如何有效,重新返回令牌信息给客户端(包含令牌和刷新令牌);
- 如果刷新令牌校验失败,则返回402(刷新令牌校验失败),此时客户端需要跳转到登录页,用户需要重新登录。