1. Minimal Dependencies for JWT
의존성 모듈 | 역할 |
spring-security-oauth2-resource-server | 리소스 서버 기본 기능 |
spring-security-oauth2-jose | JWT 디코딩 및 서명 검증 기능 |
2. Minimal Configuration for JWTs
issuer-uri
- Authorization Server의 메타데이터 엔드포인트를 자동으로 탐색 (.well-known/oauth-authorization-server/issuer)
- public key 및 검증 설정을 구성함 (jwks_uri)
동작 내용 | 설명 |
issuer-uri로 metadata endpoint 요청 |
issuer-uri 값에 따라 호출
- .well-known/oauth-authorization-server or .well-known/openid-configuration |
메타데이터에서 jwks_uri 추출 | 메타데이터(JSON)에서 jwks_uri 항목 확인 |
공개키 및 서명 알고리즘 확인 |
jwks_uri로 요청하여 키와 알고리즘 파악
|
JWT 서명 검증 전략 구성 |
키 정보와 알고리즘을 바탕으로 JwtDecoder 자동 생성
|
iss 클레임 검증 설정 |
JWT의 iss 클레임 값이 issuer-uri와 일치해야 인증 성공
|
설정) application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/issuer
3. How JWT Authentication Works
단계 | 설명 |
1️⃣ |
BearerTokenAuthenticationFilter가 요청에서 Authorization 헤더 추출 후 BearerTokenAuthenticationToken 생성
|
2️⃣ |
이 토큰이 AuthenticationManager(실제로는 ProviderManager)에 전달됨
|
3️⃣ |
ProviderManager는 내부적으로 JwtAuthenticationProvider를 사용함
|
4️⃣ |
JwtAuthenticationProvider가 JwtDecoder를 사용하여 JWT를 디코딩, 서명 검증, 클레임 검증 수행
|
5️⃣ |
검증이 통과되면, JwtAuthenticationConverter를 통해 GrantedAuthority 목록을 생성
|
6️⃣ |
최종적으로 JwtAuthenticationToken 객체가 생성되어 SecurityContextHolder에 저장됨
|
7️⃣ |
인증 성공 후 컨트롤러 등의 보호된 리소스에 접근 가능해짐
|
동작) 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
- 매핑 방식을 커스터마이징 할 수 있음
- 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) | 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 |
[Spring Security] 7-4. OAuth2 Client: Authorized Client Features (0) | 2025.07.20 |