Files
baiyue-firstleg/auth/src/main/java/com/baiyue/auth/service/OverseasTokenService.java
T

83 lines
4.2 KiB
Java

package com.baiyue.auth.service;
import com.baiyue.auth.client.AuthorizationLoader;
import com.baiyue.auth.client.AuthorizationSnapshot;
import com.baiyue.auth.config.OverseasJwtProperties;
import com.baiyue.auth.entity.AuthAccount;
import com.baiyue.auth.repository.AuthAccountRepository;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/** 将已经通过国内 HMAC 与 Redis 校验的会话兑换为海外 RS256 短时令牌。 */
@Service
@ConditionalOnProperty(name = "baiyue.security.overseas-jwt.enabled", havingValue = "true")
public class OverseasTokenService {
private static final List<String> OVERSEAS_AUTHORITY_PREFIXES = List.of(
"platform:overseas-warehouse:",
"platform:overseas-business:",
"platform:overseas-master-data:",
"lastleg:"
);
@Resource private AuthAccountRepository accounts;
@Resource private AuthorizationLoader authorizationLoader;
@Resource private OverseasJwtProperties properties;
@Resource @Qualifier("overseasJwtEncoder") private JwtEncoder encoder;
public OverseasTokenResponse exchange(long authenticatedUserId) {
AuthAccount account = accounts.findById(authenticatedUserId);
if (account == null || !"ACTIVE".equals(account.getAccountStatus())) {
throw new BizException(ResultCode.UNAUTHORIZED, "账号不可用");
}
AuthorizationSnapshot authorization = authorizationLoader.load(account);
return issue(account, authorization);
}
public OverseasTokenResponse issue(AuthAccount account, AuthorizationSnapshot authorization) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(properties.accessTokenTtl());
JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder()
.issuer(properties.issuer()).audience(List.of(properties.audience()))
.subject(account.getId().toString()).id(UUID.randomUUID().toString())
.issuedAt(issuedAt).expiresAt(expiresAt)
.claim("user_id", account.getId()).claim("tenant_id", authorization.getTenantId())
.claim("membership_id", authorization.getMembershipId())
.claim("app_code", authorization.getAppCode()).claim("client_type", authorization.getClientType())
.claim("roles", authorization.getRoles())
.claim("authorities", overseasAuthorities(authorization.getPermissions()))
.claim("token_version", account.getTokenVersion() == null ? 0 : account.getTokenVersion())
.claim("permission_version", authorization.getPermissionVersion() == null ? 0L : authorization.getPermissionVersion());
if (authorization.getOverseasWarehouseId() != null) {
claimsBuilder.claim("overseas_warehouse_id", authorization.getOverseasWarehouseId());
}
JwtClaimsSet claims = claimsBuilder.build();
String token = encoder.encode(JwtEncoderParameters.from(
JwsHeader.with(SignatureAlgorithm.RS256).keyId(properties.currentKid()).build(), claims)).getTokenValue();
return new OverseasTokenResponse("Bearer", token, properties.accessTokenTtl().toSeconds());
}
private Set<String> overseasAuthorities(Set<String> permissions) {
if (permissions == null || permissions.isEmpty()) return Set.of();
return permissions.stream()
.filter(permission -> permission != null && OVERSEAS_AUTHORITY_PREFIXES.stream().anyMatch(permission::startsWith))
.collect(Collectors.toUnmodifiableSet());
}
public record OverseasTokenResponse(String tokenType, String accessToken, long expiresIn) { }
}