🔸 TL;DR ⚡
Add spring-boot-starter-oauth2-resource-server
- ▪️ Set issuer-uri (or jwk-set-uri)
- ▪️ Protect routes + authorize with SCOPE_... (or your custom roles mapping)
🔸 WHAT “RESOURCE SERVER” MEANS (NO CONFUSION!)
Your REST API is not the login page and it doesn’t “do OAuth flows”. It simply accepts Bearer tokens (usually JWTs) issued by an Authorization Server (Keycloak, Auth0, Okta, Spring Authorization Server…) and then:
- ▪️ verifies the token signature (JWKs) ✅
- ▪️ checks expiration / issuer claims ✅
- ▪️ maps scopes → Spring authorities (often SCOPE_...) ✅
🔸 DEPENDENCIES (BOOT 3+/SECURITY 6+) Maven
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
🔸 CONFIGURE JWT VALIDATION (ISSUER OR JWK SET) application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/realms/demo
# OR (if you only have the keys endpoint)
# jwk-set-uri: https://auth.example.com/realms/demo/protocol/openid-connect/certs
🔸 LOCK DOWN ENDPOINTS WITH SecurityFilterChain
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableMethodSecurity class SecurityConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) // typical for stateless REST APIs .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/actuator/health", "/actuator/info").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .build(); } }
🔸 AUTHORIZE WITH SCOPES (THE FUN PART 🔥)
Example controller using scopes as authorities:
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/orders") class OrderController { @GetMapping @PreAuthorize("hasAuthority('SCOPE_orders:read')") String list() { return "orders"; } @PostMapping @PreAuthorize("hasAuthority('SCOPE_orders:write')") String create() { return "created"; } }
If your provider uses a different claim (scp, roles, etc.), plug your own converter:
import org.springframework.context.annotation.Bean; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; @Bean JwtAuthenticationConverter jwtAuthenticationConverter() { var scopes = new JwtGrantedAuthoritiesConverter(); scopes.setAuthorityPrefix("SCOPE_"); scopes.setAuthoritiesClaimName("scope"); // sometimes "scp" var converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(scopes); return converter; }
…and wire it:
.oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) )
🔸 TAKEAWAYS ✅
- ▪️ Resource Server = token validation, not “OAuth login”
- ▪️ Prefer issuer-uri when possible (cleaner + discovery-based)
- ▪️ Use method security (@PreAuthorize) for clear, readable authorization rules
- ▪️ Expect: missing token → 401, missing scope/role → 403 🔒
#SpringBoot #SpringSecurity #OAuth2 #ResourceServer #JWT #API #Java #DevSecOps #Backend #Security
Go further with Java certification:
Java👇
Spring👇
SpringBook👇
JavaBook👇