实现步骤
- 引入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) {return Jwts.parser().verifyWith(KEY).build().parseSignedClaims(token);}/*** 解析令牌header*/private static JwsHeader parseHeader(String token) {return parseClaim(token).getHeader();}/*** 解析令牌payload*/private static Claims parsePayload(String token) {return parseClaim(token).getPayload();}
}
生成令牌样式
{"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsIm5hbWUiOiJKYWltZSIsInR5cGUiOjAsImp0aSI6IjNjOWFhMDIxLWNkN2MtNDQ1Ni1hMTE1LTYxOWJkOTg3NzI2NCIsImV4cCI6MTcxOTA2MTQwMiwiaWF0IjoxNzE5MDYxMTAyLCJzdWIiOiJQZXJpcGhlcmFscyIsImlzcyI6Ik9jZWFuIn0.Cj_UJfbaWaiSu82ma-C1hd8L1u2_x3RRBHkXK5XFAPA","accessTokenExpireIn": 1719061402,"refreshToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsIm5hbWUiOiJKYWltZSIsInR5cGUiOjEsImp0aSI6IjU4YTVmZDAzLTlkYWYtNDY2ZS05NjMwLTQyNjU5ODZmYTViNiIsImV4cCI6MTcxOTA2ODMwMywiaWF0IjoxNzE5MDYxMTAzLCJzdWIiOiJQZXJpcGhlcmFscyIsImlzcyI6Ik9jZWFuIn0.jegxFBj4t-AJhiWY_OByEs0vtsHBb0d0vjEerVFl85E","refreshTokenExpireIn": 1719068303
}
系统认证流程
- 用户登录系统,用户有效,生成令牌返回给客户端(Web前端或APP等);
- 客户端记录令牌信息和刷新令牌信息;
- 客户端携带令牌(一般放在头信息中)去请求服务器的资源;
- 服务进行令牌校验,检验通过返回资源信息给客户端,检验失败返回401(鉴权失败)给客户端;
- 客户端收到401响应码,知道令牌失效了,此时使用刷新令牌去重新获取令牌;
- 服务校验刷新令牌是否有效,如何有效,重新返回令牌信息给客户端(包含令牌和刷新令牌);
- 如果刷新令牌校验失败,则返回402(刷新令牌校验失败),此时客户端需要跳转到登录页,用户需要重新登录。