package com.vincent.rsf.openApi.service.impl;
|
|
import com.vincent.rsf.openApi.entity.constant.Constants;
|
import com.vincent.rsf.openApi.security.service.AppAuthService;
|
import com.vincent.rsf.openApi.service.TokenService;
|
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.security.Keys;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
|
import javax.crypto.SecretKey;
|
import java.nio.charset.StandardCharsets;
|
import java.security.MessageDigest;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.Map;
|
|
/**
|
* 对接 Token:JWT(密钥来自配置,重启后同一密钥仍可校验未过期 token;claims 仅含 appId)
|
*/
|
@Slf4j
|
@Service
|
public class TokenServiceImpl implements TokenService {
|
|
private static final String CLAIM_APP_ID = "appId";
|
|
private final AppAuthService appAuthService;
|
private final SecretKey secretKey;
|
private final long expireMs;
|
|
@Autowired
|
public TokenServiceImpl(
|
AppAuthService appAuthService,
|
@Value("${open-api.jwt.secret:}") String jwtSecret,
|
@Value("${open-api.jwt.expire-seconds:3600}") long expireSeconds) {
|
this.appAuthService = appAuthService;
|
if (!StringUtils.hasText(jwtSecret)) {
|
throw new IllegalStateException("请配置 open-api.jwt.secret(HS256,建议至少 32 字符或使用环境变量覆盖)");
|
}
|
this.secretKey = hmacSha256Key(jwtSecret.trim());
|
this.expireMs = expireSeconds * 1000L;
|
}
|
|
/** 短字符串用 SHA-256 派生 32 字节以满足 HS256 */
|
private static SecretKey hmacSha256Key(String secret) {
|
byte[] bytes = secret.getBytes(StandardCharsets.UTF_8);
|
if (bytes.length < 32) {
|
try {
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
bytes = md.digest(bytes);
|
} catch (Exception e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
return Keys.hmacShaKeyFor(bytes);
|
}
|
|
@Override
|
public String issueToken(String appId, String appSecret) {
|
if (!StringUtils.hasText(appId) || !StringUtils.hasText(appSecret)) {
|
return null;
|
}
|
if (!appAuthService.validateApp(appId, appSecret)) {
|
return null;
|
}
|
long now = System.currentTimeMillis();
|
Map<String, Object> claims = new HashMap<>();
|
claims.put(CLAIM_APP_ID, appId);
|
String compact = Jwts.builder()
|
.setClaims(claims)
|
.setIssuedAt(new Date(now))
|
.setExpiration(new Date(now + expireMs))
|
.signWith(secretKey, SignatureAlgorithm.HS256)
|
.compact();
|
return Constants.TOKEN_PREFIX + compact;
|
}
|
|
@Override
|
public boolean validateToken(String token) {
|
return getAppIdIfValid(token) != null;
|
}
|
|
@Override
|
public String getAppIdIfValid(String rawToken) {
|
if (!StringUtils.hasText(rawToken)) {
|
return null;
|
}
|
try {
|
Claims claims = Jwts.parserBuilder()
|
.setSigningKey(secretKey)
|
.build()
|
.parseClaimsJws(rawToken)
|
.getBody();
|
Date exp = claims.getExpiration();
|
if (exp == null || exp.before(new Date())) {
|
return null;
|
}
|
Object appId = claims.get(CLAIM_APP_ID);
|
return appId != null ? appId.toString() : null;
|
} catch (JwtException e) {
|
log.debug("JWT 校验失败: {}", e.getMessage());
|
return null;
|
}
|
}
|
}
|