122 lines
4.2 KiB
Java
122 lines
4.2 KiB
Java
package com.yoyuzh.auth;
|
|
|
|
import com.yoyuzh.config.JwtProperties;
|
|
import io.jsonwebtoken.Claims;
|
|
import io.jsonwebtoken.Jwts;
|
|
import io.jsonwebtoken.security.Keys;
|
|
import jakarta.annotation.PostConstruct;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import javax.crypto.SecretKey;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.time.Instant;
|
|
import java.util.Date;
|
|
import org.springframework.util.StringUtils;
|
|
|
|
@Component
|
|
public class JwtTokenProvider {
|
|
|
|
private static final String DEFAULT_SECRET = "change-me-change-me-change-me-change-me";
|
|
|
|
private final JwtProperties jwtProperties;
|
|
private SecretKey secretKey;
|
|
|
|
public JwtTokenProvider(JwtProperties jwtProperties) {
|
|
this.jwtProperties = jwtProperties;
|
|
}
|
|
|
|
@PostConstruct
|
|
public void init() {
|
|
String secret = jwtProperties.getSecret() == null ? "" : jwtProperties.getSecret().trim();
|
|
if (secret.isEmpty()) {
|
|
throw new IllegalStateException("app.jwt.secret 未配置,请设置强密钥后再启动");
|
|
}
|
|
if (DEFAULT_SECRET.equals(secret)) {
|
|
throw new IllegalStateException("检测到默认 JWT 密钥,请替换 app.jwt.secret 后再启动");
|
|
}
|
|
if (secret.getBytes(StandardCharsets.UTF_8).length < 32) {
|
|
throw new IllegalStateException("JWT 密钥长度过短,至少需要 32 字节");
|
|
}
|
|
secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
|
|
public String generateAccessToken(Long userId, String username, String sessionId) {
|
|
return generateAccessToken(userId, username, sessionId, AuthClientType.DESKTOP);
|
|
}
|
|
|
|
public String generateAccessToken(Long userId, String username, String sessionId, AuthClientType clientType) {
|
|
Instant now = Instant.now();
|
|
var builder = Jwts.builder()
|
|
.subject(username)
|
|
.claim("uid", userId)
|
|
.claim("client", clientType.name())
|
|
.issuedAt(Date.from(now))
|
|
.expiration(Date.from(now.plusSeconds(jwtProperties.getAccessExpirationSeconds())))
|
|
.signWith(secretKey);
|
|
|
|
if (StringUtils.hasText(sessionId)) {
|
|
builder.claim("sid", sessionId);
|
|
}
|
|
|
|
return builder.compact();
|
|
}
|
|
|
|
public boolean validateToken(String token) {
|
|
try {
|
|
Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token);
|
|
return true;
|
|
} catch (Exception ex) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public String getUsername(String token) {
|
|
return parseClaims(token).getSubject();
|
|
}
|
|
|
|
public Long getUserId(String token) {
|
|
Object uid = parseClaims(token).get("uid");
|
|
return uid == null ? null : Long.parseLong(uid.toString());
|
|
}
|
|
|
|
public Instant getIssuedAt(String token) {
|
|
Date issuedAt = parseClaims(token).getIssuedAt();
|
|
return issuedAt == null ? null : issuedAt.toInstant();
|
|
}
|
|
|
|
public String getSessionId(String token) {
|
|
Object sessionId = parseClaims(token).get("sid");
|
|
return sessionId == null ? null : sessionId.toString();
|
|
}
|
|
|
|
public AuthClientType getClientType(String token) {
|
|
Object clientType = parseClaims(token).get("client");
|
|
return AuthClientType.fromHeader(clientType == null ? null : clientType.toString());
|
|
}
|
|
|
|
public boolean hasMatchingSession(String token, String activeSessionId) {
|
|
String tokenSessionId = getSessionId(token);
|
|
|
|
if (!StringUtils.hasText(activeSessionId)) {
|
|
return !StringUtils.hasText(tokenSessionId);
|
|
}
|
|
|
|
return activeSessionId.equals(tokenSessionId);
|
|
}
|
|
|
|
public boolean hasMatchingSession(String token, User user) {
|
|
String expectedSessionId = switch (getClientType(token)) {
|
|
case MOBILE -> user.getMobileActiveSessionId();
|
|
case DESKTOP -> StringUtils.hasText(user.getDesktopActiveSessionId())
|
|
? user.getDesktopActiveSessionId()
|
|
: user.getActiveSessionId();
|
|
};
|
|
|
|
return hasMatchingSession(token, expectedSessionId);
|
|
}
|
|
|
|
private Claims parseClaims(String token) {
|
|
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload();
|
|
}
|
|
}
|