|
|
|
package com.zhonglai.luhui.login.util;
|
|
|
|
|
|
|
|
import io.jsonwebtoken.*;
|
|
|
|
import io.jsonwebtoken.SignatureException;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
import java.security.*;
|
|
|
|
import java.security.spec.*;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* JJWT 0.9.1 工具类
|
|
|
|
* 功能:生成 token、解析 token、校验 token
|
|
|
|
*
|
|
|
|
* 说明:
|
|
|
|
* - SECRET 应妥善保管,不要硬编码到源码(可从配置文件/env读取)
|
|
|
|
* - expirationSeconds 单位为秒
|
|
|
|
*/
|
|
|
|
public class JwtUtils {
|
|
|
|
|
|
|
|
private static final String PRIVATE_KEY_PATH = "keys/rsa_private_key.pem";
|
|
|
|
private static final String PUBLIC_KEY_PATH = "keys/rsa_public_key.pem";
|
|
|
|
|
|
|
|
// Token 有效期:24 小时
|
|
|
|
private static final long EXPIRATION_SECONDS = 60 * 60 * 24;
|
|
|
|
|
|
|
|
private static PrivateKey privateKey;
|
|
|
|
private static PublicKey publicKey;
|
|
|
|
|
|
|
|
static {
|
|
|
|
try {
|
|
|
|
privateKey = loadPrivateKey(PRIVATE_KEY_PATH);
|
|
|
|
publicKey = loadPublicKey(PUBLIC_KEY_PATH);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new RuntimeException("加载 RSA 公私钥失败", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------- 生成 Token --------------------
|
|
|
|
|
|
|
|
public static String generateToken(Map<String, Object> claims, String subject) {
|
|
|
|
|
|
|
|
return generateToken(claims,subject,EXPIRATION_SECONDS);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String generateToken(Map<String, Object> claims, String subject,long EXPIRATION_SECONDS) {
|
|
|
|
Date now = new Date();
|
|
|
|
Date expiryDate = new Date(now.getTime() + EXPIRATION_SECONDS * 1000L);
|
|
|
|
|
|
|
|
return Jwts.builder()
|
|
|
|
.setClaims(claims)
|
|
|
|
.setSubject(subject)
|
|
|
|
.setIssuedAt(now)
|
|
|
|
.setExpiration(expiryDate)
|
|
|
|
.signWith(SignatureAlgorithm.RS256, privateKey)
|
|
|
|
.compact();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String generateToken(String subject) {
|
|
|
|
return generateToken(null, subject);
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------- 解析 Token --------------------
|
|
|
|
|
|
|
|
private static Claims getAllClaimsFromToken(String token) throws JwtException {
|
|
|
|
return Jwts.parser()
|
|
|
|
.setSigningKey(publicKey)
|
|
|
|
.parseClaimsJws(token)
|
|
|
|
.getBody();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
|
|
|
|
Claims claims = getAllClaimsFromToken(token);
|
|
|
|
return claimsResolver.apply(claims);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getSubjectFromToken(String token) {
|
|
|
|
try {
|
|
|
|
return getClaimFromToken(token, Claims::getSubject);
|
|
|
|
} catch (ExpiredJwtException e) {
|
|
|
|
return e.getClaims().getSubject();
|
|
|
|
} catch (JwtException e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Object getCustomClaim(String token, String key) {
|
|
|
|
try {
|
|
|
|
Claims claims = getAllClaimsFromToken(token);
|
|
|
|
return claims.get(key);
|
|
|
|
} catch (JwtException e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------- 验证 Token --------------------
|
|
|
|
|
|
|
|
public static boolean isTokenExpired(String token) {
|
|
|
|
try {
|
|
|
|
Date expiration = getClaimFromToken(token, Claims::getExpiration);
|
|
|
|
return expiration.before(new Date());
|
|
|
|
} catch (ExpiredJwtException e) {
|
|
|
|
return true;
|
|
|
|
} catch (JwtException e) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean validateToken(String token, String expectedSubject) {
|
|
|
|
try {
|
|
|
|
Claims claims = getAllClaimsFromToken(token);
|
|
|
|
String subject = claims.getSubject();
|
|
|
|
Date expiration = claims.getExpiration();
|
|
|
|
return subject.equals(expectedSubject) && expiration.after(new Date());
|
|
|
|
} catch (SignatureException e) {
|
|
|
|
System.err.println("签名验证失败: " + e.getMessage());
|
|
|
|
return false;
|
|
|
|
} catch (JwtException e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean validateTokenSignatureAndExpiry(String token) {
|
|
|
|
try {
|
|
|
|
Claims claims = getAllClaimsFromToken(token);
|
|
|
|
return claims.getExpiration().after(new Date());
|
|
|
|
} catch (JwtException e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------- 其他方法 --------------------
|
|
|
|
|
|
|
|
public static String getUserIdFromToken(String token) {
|
|
|
|
Object userId = getCustomClaim(token, "userId");
|
|
|
|
return userId != null ? userId.toString() : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String refreshToken(String token) {
|
|
|
|
try {
|
|
|
|
Claims claims = getAllClaimsFromToken(token);
|
|
|
|
return generateToken(claims, claims.getSubject());
|
|
|
|
} catch (JwtException e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------- RSA 密钥加载 --------------------
|
|
|
|
|
|
|
|
private static PrivateKey loadPrivateKey(String filePath) throws IOException, GeneralSecurityException {
|
|
|
|
String key = new String(Files.readAllBytes(Paths.get(filePath)))
|
|
|
|
.replaceAll("-----BEGIN (.*)-----", "")
|
|
|
|
.replaceAll("-----END (.*)----", "")
|
|
|
|
.replaceAll("\\s+", "");
|
|
|
|
byte[] keyBytes = java.util.Base64.getDecoder().decode(key);
|
|
|
|
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
|
|
|
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
|
|
|
return kf.generatePrivate(spec);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static PublicKey loadPublicKey(String filePath) throws IOException, GeneralSecurityException {
|
|
|
|
String key = new String(Files.readAllBytes(Paths.get(filePath)))
|
|
|
|
.replaceAll("-----BEGIN (.*)-----", "")
|
|
|
|
.replaceAll("-----END (.*)----", "")
|
|
|
|
.replaceAll("\\s+", "");
|
|
|
|
byte[] keyBytes = java.util.Base64.getDecoder().decode(key);
|
|
|
|
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
|
|
|
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
|
|
|
return kf.generatePublic(spec);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
Map<String, Object> claims = new HashMap<>();
|
|
|
|
claims.put("userId", 1001);
|
|
|
|
claims.put("role", "admin");
|
|
|
|
|
|
|
|
String token = generateToken(null, "alice");
|
|
|
|
System.out.println("Token: " + token);
|
|
|
|
|
|
|
|
String subject = getSubjectFromToken(token);
|
|
|
|
System.out.println("Subject: " + subject);
|
|
|
|
|
|
|
|
String role = (String) getCustomClaim(token, "role");
|
|
|
|
System.out.println("Role: " + role);
|
|
|
|
|
|
|
|
boolean valid = validateToken(token, "alice");
|
|
|
|
System.out.println("Token 有效: " + valid);
|
|
|
|
}
|
|
|
|
} |
...
|
...
|
|