Skip to content
Back to skills

Oauth2 Resource Server

ASecurity

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.

  • 272 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 6, 2026
ai-agentsgojavaspringapisecurity

Works with

  • api

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned September 6, 2026

npx -y skills add rrezartprebreza/spring-boot-skills --skill oauth2-resource-server --agent claude-code

Installs 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 with every re-scan.

Security grade badge for Oauth2 Resource Server
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rrezartprebreza-oauth2-resource-server-spring-boot-skills/badge)](https://www.skillsdirectory.com/skills/rrezartprebreza-oauth2-resource-server-spring-boot-skills)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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`)

Files in this skill

  • SKILL.md5.4 KB
  • agents/openai.yaml207 B
  • examples/bad-resource-server-config.java2.1 KB
  • examples/good-resource-server-config.java1.6 KB
  • templates/JwtClaimExtractor.java1.7 KB
  • templates/ResourceServerConfig.java3.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.