feat: scope-based authorization — read standard scope claim, remove custom roles extraction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,9 +34,6 @@ public class MeController {
|
|||||||
|
|
||||||
String orgId = jwt.getClaimAsString("organization_id");
|
String orgId = jwt.getClaimAsString("organization_id");
|
||||||
|
|
||||||
List<String> globalRoles = jwt.getClaimAsStringList("roles");
|
|
||||||
boolean isPlatformAdmin = globalRoles != null && globalRoles.contains("platform-admin");
|
|
||||||
|
|
||||||
if (orgId != null) {
|
if (orgId != null) {
|
||||||
var tenant = tenantService.getByLogtoOrgId(orgId).orElse(null);
|
var tenant = tenantService.getByLogtoOrgId(orgId).orElse(null);
|
||||||
List<Map<String, Object>> tenants = tenant != null
|
List<Map<String, Object>> tenants = tenant != null
|
||||||
@@ -49,7 +46,6 @@ public class MeController {
|
|||||||
|
|
||||||
return ResponseEntity.ok(Map.of(
|
return ResponseEntity.ok(Map.of(
|
||||||
"userId", userId,
|
"userId", userId,
|
||||||
"isPlatformAdmin", isPlatformAdmin,
|
|
||||||
"tenants", tenants));
|
"tenants", tenants));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +63,6 @@ public class MeController {
|
|||||||
|
|
||||||
return ResponseEntity.ok(Map.of(
|
return ResponseEntity.ok(Map.of(
|
||||||
"userId", userId,
|
"userId", userId,
|
||||||
"isPlatformAdmin", isPlatformAdmin,
|
|
||||||
"tenants", tenants));
|
"tenants", tenants));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,17 +62,14 @@ public class SecurityConfig {
|
|||||||
var converter = new JwtAuthenticationConverter();
|
var converter = new JwtAuthenticationConverter();
|
||||||
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
|
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
|
||||||
List<GrantedAuthority> authorities = new ArrayList<>();
|
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||||
|
String scope = jwt.getClaimAsString("scope");
|
||||||
var roles = jwt.getClaimAsStringList("roles");
|
if (scope != null) {
|
||||||
if (roles != null) {
|
for (String s : scope.split(" ")) {
|
||||||
roles.forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_" + r)));
|
if (!s.isBlank()) {
|
||||||
|
authorities.add(new SimpleGrantedAuthority("SCOPE_" + s));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var orgRoles = jwt.getClaimAsStringList("organization_roles");
|
|
||||||
if (orgRoles != null) {
|
|
||||||
orgRoles.forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_org_" + r)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorities;
|
return authorities;
|
||||||
});
|
});
|
||||||
return converter;
|
return converter;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class TenantController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("hasRole('platform-admin')")
|
@PreAuthorize("hasAuthority('SCOPE_platform:admin')")
|
||||||
public ResponseEntity<List<TenantResponse>> listAll() {
|
public ResponseEntity<List<TenantResponse>> listAll() {
|
||||||
List<TenantResponse> tenants = tenantService.findAll().stream()
|
List<TenantResponse> tenants = tenantService.findAll().stream()
|
||||||
.map(this::toResponse).toList();
|
.map(this::toResponse).toList();
|
||||||
@@ -36,7 +36,7 @@ public class TenantController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("hasRole('platform-admin')")
|
@PreAuthorize("hasAuthority('SCOPE_platform:admin')")
|
||||||
public ResponseEntity<TenantResponse> create(@Valid @RequestBody CreateTenantRequest request,
|
public ResponseEntity<TenantResponse> create(@Valid @RequestBody CreateTenantRequest request,
|
||||||
Authentication authentication) {
|
Authentication authentication) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.springframework.security.oauth2.jwt.Jwt;
|
|||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@TestConfiguration
|
@TestConfiguration
|
||||||
public class TestSecurityConfig {
|
public class TestSecurityConfig {
|
||||||
@@ -20,8 +19,7 @@ public class TestSecurityConfig {
|
|||||||
.claim("sub", "test-user")
|
.claim("sub", "test-user")
|
||||||
.claim("iss", "https://test-issuer.example.com/oidc")
|
.claim("iss", "https://test-issuer.example.com/oidc")
|
||||||
.claim("organization_id", "test-org-id")
|
.claim("organization_id", "test-org-id")
|
||||||
.claim("roles", List.of("platform-admin"))
|
.claim("scope", "platform:admin tenant:manage apps:manage apps:deploy observe:read observe:debug secrets:manage billing:manage team:manage settings:manage")
|
||||||
.claim("organization_roles", List.of("admin"))
|
|
||||||
.issuedAt(Instant.now())
|
.issuedAt(Instant.now())
|
||||||
.expiresAt(Instant.now().plusSeconds(3600))
|
.expiresAt(Instant.now().plusSeconds(3600))
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ class LicenseControllerTest {
|
|||||||
var result = mockMvc.perform(post("/api/tenants")
|
var result = mockMvc.perform(post("/api/tenants")
|
||||||
.with(jwt().jwt(j -> j
|
.with(jwt().jwt(j -> j
|
||||||
.claim("sub", "test-user")
|
.claim("sub", "test-user")
|
||||||
.claim("roles", java.util.List.of("platform-admin")))
|
.claim("scope", "platform:admin"))
|
||||||
.authorities(new SimpleGrantedAuthority("ROLE_platform-admin")))
|
.authorities(new SimpleGrantedAuthority("SCOPE_platform:admin")))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(request)))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ class TenantControllerTest {
|
|||||||
.with(jwt().jwt(j -> j
|
.with(jwt().jwt(j -> j
|
||||||
.claim("sub", "test-user")
|
.claim("sub", "test-user")
|
||||||
.claim("organization_id", "test-org")
|
.claim("organization_id", "test-org")
|
||||||
.claim("roles", java.util.List.of("platform-admin")))
|
.claim("scope", "platform:admin"))
|
||||||
.authorities(new SimpleGrantedAuthority("ROLE_platform-admin")))
|
.authorities(new SimpleGrantedAuthority("SCOPE_platform:admin")))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(request)))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
@@ -59,8 +59,8 @@ class TenantControllerTest {
|
|||||||
mockMvc.perform(post("/api/tenants")
|
mockMvc.perform(post("/api/tenants")
|
||||||
.with(jwt().jwt(j -> j
|
.with(jwt().jwt(j -> j
|
||||||
.claim("sub", "test-user")
|
.claim("sub", "test-user")
|
||||||
.claim("roles", java.util.List.of("platform-admin")))
|
.claim("scope", "platform:admin"))
|
||||||
.authorities(new SimpleGrantedAuthority("ROLE_platform-admin")))
|
.authorities(new SimpleGrantedAuthority("SCOPE_platform:admin")))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(request)))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isCreated());
|
.andExpect(status().isCreated());
|
||||||
@@ -68,8 +68,8 @@ class TenantControllerTest {
|
|||||||
mockMvc.perform(post("/api/tenants")
|
mockMvc.perform(post("/api/tenants")
|
||||||
.with(jwt().jwt(j -> j
|
.with(jwt().jwt(j -> j
|
||||||
.claim("sub", "test-user")
|
.claim("sub", "test-user")
|
||||||
.claim("roles", java.util.List.of("platform-admin")))
|
.claim("scope", "platform:admin"))
|
||||||
.authorities(new SimpleGrantedAuthority("ROLE_platform-admin")))
|
.authorities(new SimpleGrantedAuthority("SCOPE_platform:admin")))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(request)))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isConflict());
|
.andExpect(status().isConflict());
|
||||||
@@ -93,8 +93,8 @@ class TenantControllerTest {
|
|||||||
var createResult = mockMvc.perform(post("/api/tenants")
|
var createResult = mockMvc.perform(post("/api/tenants")
|
||||||
.with(jwt().jwt(j -> j
|
.with(jwt().jwt(j -> j
|
||||||
.claim("sub", "test-user")
|
.claim("sub", "test-user")
|
||||||
.claim("roles", java.util.List.of("platform-admin")))
|
.claim("scope", "platform:admin"))
|
||||||
.authorities(new SimpleGrantedAuthority("ROLE_platform-admin")))
|
.authorities(new SimpleGrantedAuthority("SCOPE_platform:admin")))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(request)))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
|
|||||||
Reference in New Issue
Block a user