1. Minimal Dependencies for JWT
| 의존성 모듈 | 역할 |
| spring-security-oauth2-resource-server | 리소스 서버 기본 기능 |
| spring-security-oauth2-jose | JWT 디코딩 및 서명 검증 기능 |
2. Minimal Configuration for JWTs
issuer-uri
- 토큰을 발급한 Authorization Server의 발급자 주소
| 단계 | 동작 | 의미 |
| 1 | issuer-uri 기반으로 메타데이터 엔드포인트 호출 | 인증 서버 정보 조회 |
| 2 | 메타데이터에서 jwks_uri 확인 | 공개키 목록 위치 찾기 |
| 3 | jwks_uri로 공개키 조회 | JWT 서명 검증용 키 확보 |
| 4 | JwtDecoder 자동 구성 | JWT 검증기 생성 |
| 5 | JWT의 iss 클레임 검증 | 설정한 issuer와 토큰 issuer가 같은지 확인 |
| 6 | exp, nbf, 서명 등 검증 | 만료·위조 여부 확인 |
설정) application.yml
더보기
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/issuer
3. How JWT Authentication Works

- BearerTokenAuthenticationFilter
- Authorization 헤더를 추출하고 BearerTokenAuthenticationToken을 생성함
- 이 토큰이 AuthenticationManager에 전달됨
- 내부적으로 JwtAuthenticationProvider를 사용함
- JwtAuthenticationProvider
- JwtDecoder를 사용하여 JWT 검증을 수행함
- JwtAuthenticationConverter: 검증이 통과되면 GrantedAuthority 목록을 생성함
- ➡️ JwtAuthenticationToken 객체가 생성되어 SecurityContextHolder에 저장됨
- 인증 성공 후 컨트롤러 등의 보호된 리소스에 접근 가능해짐
동작) JwtAuthenticationProvider
더보기
| 동작 내용 | 설명 |
| JWT 서명 검증 |
JWT의 서명을 jwks_uri에서 받은 공개키로 검증
|
| 클레임 검증 |
exp(만료), nbf(Not Before), iss(발급자) 값 검증
|
| scope 매핑 |
scope 값 → SCOPE_ 접두어 붙여 권한(GrantedAuthority)으로 변환
|
| Authentication.getPrincipal() → Jwt | principal 객체는 JWT 자체 |
| Authentication.getName() → sub | sub 클레임 값이 사용자명처럼 사용됨 |
4. Specifying the Authorization Server JWK Set Uri Directly
jwk-set-uri
- 공개키를 직접 가져와 JWT 서명 검증을 수행
5. Supplying Audiences
aud
- 토큰이 의도된 대상 (Resource Server)
6. Overriding or Replacing Boot Auto Configuration
- Spring Boot에 의해 자동설정됨
| Bean | 설명 | 코드 |
| SecurityFilterChain |
모든 요청을 JWT로 인증하도록 구성
|
.oauth2ResourceServer().jwt()
|
| JwtDecoder |
issuer-uri를 기반으로 JWT 검증용 디코더 생성
|
|
설정) 자동 설정
더보기
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
커스터마이징) 설정
더보기
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/messages/**").access(hasScope("message:read"))
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwkSetUri("https://idp.example.com/jwks.json")
.jwtAuthenticationConverter(myConverter())
.jwt.decoder(myCustomDecoder())
)
);
return http.build();
}
커스터마이징) jwtDecoder
더보기
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("https://idp.example.com/jwks.json").build();
}
Configuring Trusted Algorithms
Via Spring Boot
Using a Builder
From JWK Set response
Trusting a Single Asymmetric Key
Via Spring Boot
Using a Builder
Trusting a Single Symmetric Key
Configuring Authorization
- JWT의 scope 클레임을 GrantedAuthority로 매핑
- 모든 scope 값은 SCOPE_ 접두어가 붙은 GrantedAuthority로 변환됨
- 권한 적용 시, hasScope()를 활용함 (OAuth2AuthorizationManagers)
설정) 요청 경로에 따라 권한 적용
더보기
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/contacts/**").access(hasScope("contacts"))
.requestMatchers("/messages/**").access(hasScope("messages"))
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
- hasScope("contacts")는 내부적으로 hasAuthority("SCOPE_contacts")와 같음
설정) 메서드 레벨에서 인가 설정
더보기
@PreAuthorize("hasAuthority('SCOPE_messages')")
public List<Message> getMessages() {
...
}
- 메서드 호출 시 JWT에 "scope": "messages" 가 포함되어 있어야 함
- Spring Security Method Security 활성화 필요 (@EnableMethodSecurity)
Extracting Authorities Manually
- 매핑 방식을 커스터마이징 할 수 있음
- ✅ex) scope 클레임 이름 변경, 접두어 변경 등
커스터마이징) JwtAuthenticationConverter
더보기
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName("authorities"); // <-- 클레임 이름 변경
converter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthConverter = new JwtAuthenticationConverter();
jwtAuthConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtAuthConverter;
}
설정) JwtAuthenticationConverter
더보기
http
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
);
Configuring Validation
Customizing Timestamp Validation
서버 간 시간이 약간 다를 수 있어서 JWT의 nbf와 exp 검증에서 오차 허용 필요
커스터마이징) JwtDecoder (JwtTimestampValidator)
더보기
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(Duration.ofSeconds(60)), // 60초 clock skew 허용
new JwtIssuerValidator(issuerUri)
);
jwtDecoder.setJwtValidator(withClockSkew);
return jwtDecoder;
}
Configuring RFC 9068 Validation
RFC 9068에 맞춘 JWT 유효성 검증
커스터마이징) JwtDecoder (claim: aud, clientId, issuer 등)
더보기
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuerUri)
.validateTypes(false) // typ 헤더 검증 비활성화
.build();
jwtDecoder.setJwtValidator(
JwtValidators.createAtJwtValidator()
.audience("https://audience.example.org")
.clientId("client-identifier")
.issuer("https://issuer.example.org")
.build()
);
return jwtDecoder;
}
Configuring a Custom Validator
직접 검증 로직을 작성할 수 있음
커스터마이징) OAuth2TokenValidator
더보기
static class AudienceValidator implements OAuth2TokenValidator<Jwt> {
OAuth2Error error = new OAuth2Error("custom_code", "Custom error message", null);
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (jwt.getAudience().contains("messaging")) {
return OAuth2TokenValidatorResult.success();
} else {
return OAuth2TokenValidatorResult.failure(error);
}
}
}
커스터마이징) JwtDecoder (validator)
더보기
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> audienceValidator = audienceValidator(); // 위에서 만든 커스텀 validator
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
OAuth2TokenValidator<Jwt> combined = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(combined);
return jwtDecoder;
}
Configuring Claim Set Mapping
Customizing the Conversion of a Single Claim
Adding a Claim
Removing a Claim
Renaming a Claim
Configuring Timeouts
'Spring > Spring Security' 카테고리의 다른 글
| [Spring Security] 8-1. OAuth2 Resource Server (0) | 2026.05.15 |
|---|---|
| [Spring Security] 8-3. OAuth2 Resource Server: 동작 방식 (0) | 2025.07.30 |
| [Spring Authorization Server] 2. Core Model / Components (2) | 2025.07.24 |
| [JWT] 2-3. Practical Application: OAuth2 & OpenID (0) | 2025.07.22 |
| [Spring Authorization Server] 1. Configuration Model (0) | 2025.07.20 |