package com.baiyue.common.security; import org.junit.jupiter.api.DisplayName; 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; @DisplayName("JWT 声明映射功能") class JwtClaimsMapperTest { @Test void mapsTenantUserRolesAndPermissions() { Jwt jwt = Jwt.withTokenValue("token") .header("alg", "none") .subject("subject") .issuedAt(Instant.now()) .expiresAt(Instant.now().plusSeconds(60)) .claim("user_id", 42L) .claim("tenant_id", "7") .claim("username", "alice") .claim("app_code", "MERCHANT") .claim("client_type", "MERCHANT") .claim("permissions", List.of("order:read", "order:create")) .claim("roles", List.of("ADMIN")) .build(); AccessTokenSession session = new AccessTokenSession(1, 42L, java.util.Set.of("ADMIN"), java.util.Set.of("order:read", "order:create")); BaiyueJwtAuthenticationToken authentication = new JwtClaimsMapper().map(jwt, session); assertThat(authentication.getPrincipal().userId()).isEqualTo(42L); assertThat(authentication.getPrincipal().tenantId()).isEqualTo(7L); assertThat(authentication.getAuthorities()).extracting("authority") .contains("order:read", "order:create", "ROLE_ADMIN", "APP_MERCHANT"); } @Test void ignoresAuthorizationClaimsInJwt() { Jwt jwt = Jwt.withTokenValue("token").header("alg", "none").subject("42") .issuedAt(Instant.now()).expiresAt(Instant.now().plusSeconds(60)) .claim("user_id", 42L).claim("tenant_id", 0L).claim("app_code", "PLATFORM") .claim("permissions", List.of("platform:forged")) .claim("roles", List.of("FORGED")).build(); BaiyueJwtAuthenticationToken authentication = new JwtClaimsMapper().map(jwt, new AccessTokenSession(1, 42L, java.util.Set.of(), java.util.Set.of("platform:real"))); assertThat(authentication.getAuthorities()).extracting("authority") .contains("platform:real", "APP_PLATFORM") .doesNotContain("platform:forged", "ROLE_FORGED"); } @Test void fallsBackToNumericSubjectForUserId() { Jwt jwt = Jwt.withTokenValue("token") .header("alg", "none") .subject("99") .issuedAt(Instant.now()) .expiresAt(Instant.now().plusSeconds(60)) .claim("tenant_id", 0L) .claim("app_code", "PLATFORM") .build(); BaiyueJwtAuthenticationToken authentication = new JwtClaimsMapper().map(jwt); assertThat(authentication.getPrincipal().userId()).isEqualTo(99L); assertThat(authentication.getAuthorities()).extracting("authority").contains("APP_PLATFORM"); } @Test void mapsImpersonationAndForcedPasswordClaims() { Jwt jwt = Jwt.withTokenValue("token") .header("alg", "none") .subject("99") .issuedAt(Instant.now()) .expiresAt(Instant.now().plusSeconds(60)) .claim("tenant_id", 7L) .claim("app_code", "MERCHANT") .claim("impersonated", true) .claim("actor_user_id", 42L) .claim("actor_membership_id", 43L) .claim("password_change_required", true) .build(); AuthenticatedUser user = new JwtClaimsMapper().map(jwt).getPrincipal(); assertThat(user.impersonated()).isTrue(); assertThat(user.actorUserId()).isEqualTo(42L); assertThat(user.actorMembershipId()).isEqualTo(43L); assertThat(user.passwordChangeRequired()).isTrue(); } }