Use when configuring Spring Boot as an OAuth2 resource server, validating JWTs from an external auth provider (Keycloak, Auth0, Okta, Cognito), extracting claims, or implementing scope-based authorization.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add rrezartprebreza/spring-boot-skills --skill oauth2-resource-server --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Oauth2 Resource Server?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/rrezartprebreza-oauth2-resource-server-spring-boot-skills)More formats (shields.io, HTML) on the badges page.
---
name: oauth2-resource-server
description: >
Use when configuring Spring Boot as an OAuth2 resource server, validating JWTs from
an external auth provider (Keycloak, Auth0, Okta, Cognito), extracting claims, or
implementing scope-based authorization.
---
# OAuth2 Resource Server
Spring Boot 4.x ships **Spring Security 7** — lambda DSL only; `and()`, `authorizeRequests()`,
`antMatchers()`, and `AntPathRequestMatcher`/`MvcRequestMatcher` are gone
(`requestMatchers("/path/**")` is backed by `PathPatternRequestMatcher`).
## Dependency
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-oauth2-resource-server</artifactId>
</dependency>
```
## Security Configuration
```java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/api/v1/admin/**").hasAuthority("SCOPE_admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter()))
)
.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthConverter() {
var converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName("roles"); // Keycloak uses "roles"
converter.setAuthorityPrefix("ROLE_");
var authConverter = new JwtAuthenticationConverter();
authConverter.setJwtGrantedAuthoritiesConverter(converter);
return authConverter;
}
}
```
## application.yml — Common Providers
```yaml
# Keycloak
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://keycloak.example.com/realms/my-realm
jwk-set-uri: https://keycloak.example.com/realms/my-realm/protocol/openid-connect/certs
# Auth0
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://your-domain.auth0.com/
audiences: https://your-api.example.com # custom claim validation
```
## Custom Claim Extraction
```java
@Component
public class JwtClaimExtractor {
public UUID getUserId(JwtAuthenticationToken token) {
return UUID.fromString(token.getToken().getClaimAsString("sub"));
}
public String getEmail(JwtAuthenticationToken token) {
return token.getToken().getClaimAsString("email");
}
public List<String> getRoles(JwtAuthenticationToken token) {
// Keycloak nests roles under realm_access.roles
Map<String, Object> realmAccess = token.getToken().getClaimAsMap("realm_access");
if (realmAccess == null) return List.of();
return (List<String>) realmAccess.getOrDefault("roles", List.of());
}
}
```
## Controller — Accessing Current User
```java
@RestController
@RequiredArgsConstructor
public class OrderController {
@GetMapping("/api/v1/orders/my")
public ApiResponse<List<OrderResponse>> myOrders(
@AuthenticationPrincipal Jwt jwt // inject JWT directly
) {
UUID userId = UUID.fromString(jwt.getSubject());
return ApiResponse.ok(orderService.findByUser(userId));
}
// Or with JwtAuthenticationToken for full principal
@GetMapping("/api/v1/profile")
public ApiResponse<ProfileResponse> profile(JwtAuthenticationToken token) {
return ApiResponse.ok(userService.findByEmail(
token.getToken().getClaimAsString("email")
));
}
}
```
## Method Security with Scopes
```java
@PreAuthorize("hasAuthority('SCOPE_orders:read')")
public List<Order> findAll() { ... }
@PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(#orderId, authentication)")
public Order findById(UUID orderId) { ... }
// Custom security bean
@Component("orderSecurity")
public class OrderSecurityService {
public boolean isOwner(UUID orderId, Authentication auth) {
Jwt jwt = (Jwt) auth.getPrincipal();
UUID userId = UUID.fromString(jwt.getSubject());
return orderRepository.existsByIdAndCustomerId(orderId, userId);
}
}
```
## Gotchas
- Agent uses `hasRole("ADMIN")` for scope check — scopes use `hasAuthority("SCOPE_admin")`
- Agent forgets `issuer-uri` validation — always configure to prevent token forgery
- Agent maps roles wrong for Keycloak — roles are nested under `realm_access.roles`
- Agent uses `getPrincipal()` directly — cast to `Jwt` or use `@AuthenticationPrincipal Jwt`
- Agent adds `userDetailsService` bean — not needed for resource servers (stateless JWT)
- Agent adds the Boot 3 starter `spring-boot-starter-oauth2-resource-server` — Boot 4 renamed security starters; use `spring-boot-starter-security-oauth2-resource-server`
- Agent uses non-lambda chaining (`.oauth2ResourceServer().jwt()`, `.and()`) — removed in Security 7, won't compile; lambda DSL only
- Agent writes `antMatchers()` / `AntPathRequestMatcher` — removed in Security 7; use `requestMatchers("/path/**")` (backed by `PathPatternRequestMatcher`)
- Agent mocks `JwtDecoder` with `@MockBean` in tests — removed in Boot 4; use `@MockitoBean` (and `@AutoConfigureMockMvc` — `@SpringBootTest` no longer provides `MockMvc`)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!