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 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; } } }