优化
This commit is contained in:
@@ -53,7 +53,6 @@ public class GatewaySecurityConfig {
|
||||
.withJwkSetUri(properties.jwkSetUri())
|
||||
.jwsAlgorithms(algorithms -> {
|
||||
algorithms.add(SignatureAlgorithm.RS256);
|
||||
algorithms.add(SignatureAlgorithm.ES256);
|
||||
})
|
||||
.build();
|
||||
decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
|
||||
|
||||
+39
-1
@@ -5,12 +5,14 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Validates the identity and authorization claims required by overseas APIs. */
|
||||
final class RequiredClaimsJwtValidator implements OAuth2TokenValidator<Jwt> {
|
||||
private static final List<String> REQUIRED_CLAIMS = List.of(
|
||||
"sub", "tenant_id", "app_code", "permission_version");
|
||||
"sub", "tenant_id", "app_code", "permission_version", "authorities");
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt jwt) {
|
||||
@@ -21,6 +23,42 @@ final class RequiredClaimsJwtValidator implements OAuth2TokenValidator<Jwt> {
|
||||
"invalid_token", "JWT 缺少必要声明: " + claim, null));
|
||||
}
|
||||
}
|
||||
Long subject = longValue(jwt.getSubject());
|
||||
Long tenantId = longValue(jwt.getClaims().get("tenant_id"));
|
||||
Long permissionVersion = longValue(jwt.getClaims().get("permission_version"));
|
||||
String appCode = String.valueOf(jwt.getClaims().get("app_code")).toUpperCase(Locale.ROOT);
|
||||
if (subject == null || subject <= 0) {
|
||||
return failure("JWT sub 必须是正整数用户 ID");
|
||||
}
|
||||
if (tenantId == null || tenantId < 0) {
|
||||
return failure("JWT tenant_id 必须是非负整数客户范围");
|
||||
}
|
||||
if (permissionVersion == null || permissionVersion < 0) {
|
||||
return failure("JWT permission_version 必须是非负整数");
|
||||
}
|
||||
if (!("PLATFORM".equals(appCode) && tenantId == 0L)
|
||||
&& !("MERCHANT".equals(appCode) && tenantId > 0L)) {
|
||||
return failure("JWT app_code 与 tenant_id 访问域不一致");
|
||||
}
|
||||
Object authorities = jwt.getClaims().get("authorities");
|
||||
if (!(authorities instanceof Collection<?> values)
|
||||
|| values.stream().anyMatch(value -> value == null || value.toString().isBlank())) {
|
||||
return failure("JWT authorities 必须是权限列表");
|
||||
}
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
}
|
||||
|
||||
private static Long longValue(Object value) {
|
||||
if (value instanceof Number number) return number.longValue();
|
||||
if (value == null) return null;
|
||||
try {
|
||||
return Long.valueOf(value.toString());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static OAuth2TokenValidatorResult failure(String message) {
|
||||
return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", message, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ springdoc:
|
||||
swagger-ui:
|
||||
urls:
|
||||
- { name: lastleg-merchant, url: /lastleg-service/v3/api-docs/merchant }
|
||||
- { name: lastleg-platform, url: /lastleg-service/v3/api-docs/platform }
|
||||
- { name: warehouse-merchant, url: /warehouse-overseas-service/v3/api-docs/merchant }
|
||||
- { name: warehouse-platform, url: /warehouse-overseas-service/v3/api-docs/platform }
|
||||
- { name: file-platform, url: /file-service/v3/api-docs/platform }
|
||||
- { name: file-merchant, url: /file-service/v3/api-docs/merchant }
|
||||
- { name: print-platform, url: /print-service/v3/api-docs/platform }
|
||||
|
||||
+47
-2
@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -20,14 +21,58 @@ class RequiredClaimsJwtValidatorTest {
|
||||
assertThat(validator.validate(jwt(false)).hasErrors()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMissingAuthorities() {
|
||||
Jwt value = Jwt.withTokenValue("token").header("alg", "RS256").subject("1001")
|
||||
.issuedAt(Instant.now()).expiresAt(Instant.now().plusSeconds(300))
|
||||
.claim("tenant_id", "0").claim("app_code", "PLATFORM").claim("permission_version", 3L)
|
||||
.build();
|
||||
assertThat(validator.validate(value).hasErrors()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsEmptyAuthoritySnapshotAndLeavesAuthorizationToControllers() {
|
||||
assertThat(validator.validate(jwt("0", "PLATFORM", List.of(), true)).hasErrors()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsMerchantIdentityOnlyWithPositiveCustomerScope() {
|
||||
assertThat(validator.validate(jwt("2001", "MERCHANT", List.of("merchant:order:list"), true)).hasErrors())
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsApplicationAndCustomerScopeMismatch() {
|
||||
assertThat(validator.validate(jwt("2001", "PLATFORM", List.of("platform:test:list"), true)).hasErrors())
|
||||
.isTrue();
|
||||
assertThat(validator.validate(jwt("0", "MERCHANT", List.of("merchant:test:list"), true)).hasErrors())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMalformedIdentityAndPermissionVersion() {
|
||||
Jwt value = Jwt.withTokenValue("token").header("alg", "RS256").subject("not-a-user")
|
||||
.issuedAt(Instant.now()).expiresAt(Instant.now().plusSeconds(300))
|
||||
.claim("tenant_id", "0").claim("app_code", "PLATFORM").claim("permission_version", -1L)
|
||||
.claim("authorities", List.of()).build();
|
||||
assertThat(validator.validate(value).hasErrors()).isTrue();
|
||||
}
|
||||
|
||||
private Jwt jwt(boolean includePermissionVersion) {
|
||||
return jwt("0", "PLATFORM",
|
||||
List.of("platform:overseas-warehouse:inventory:list"), includePermissionVersion);
|
||||
}
|
||||
|
||||
private Jwt jwt(String tenantId, String appCode, List<String> authorities,
|
||||
boolean includePermissionVersion) {
|
||||
Jwt.Builder builder = Jwt.withTokenValue("token")
|
||||
.header("alg", "RS256")
|
||||
.subject("1001")
|
||||
.issuedAt(Instant.now())
|
||||
.expiresAt(Instant.now().plusSeconds(300))
|
||||
.claim("tenant_id", "2001")
|
||||
.claim("app_code", "PLATFORM");
|
||||
.claim("tenant_id", tenantId)
|
||||
.claim("app_code", appCode)
|
||||
.claim("authorities", authorities);
|
||||
if (includePermissionVersion) {
|
||||
builder.claim("permission_version", 3L);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user