728x90
이 글에서는 Keycloak 26과 Spring Boot 기반의 리소스 서버 간의 JWT 연동 방법을 설명합니다. 특히, 리소스 서버가 Keycloak에 클라이언트로 별도 등록되지 않고도 JWT를 검증하는 방법, JWT 파싱 컨버터 설정 및 기본 보안 설정을 다룹니다.
- 리소스 서버는 Keycloak에 클라이언트로 등록될 필요가 없습니다.
- JWT의 iss 클레임과 리소스 서버의 issuer-uri 설정이 정확히 일치해야 합니다.
- 리소스 서버는 {issuer-uri}/protocol/openid-connect/certs를 통해
- JWT 파싱을 위한 컨버터 설정이 필요합니다.
- 간단한 Security 설정을 진행합니다.
Spring Boot 프로젝트 의존성 설정
Gradle 설정 예시 (build.gradle.kts)
dependencies {
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-security")
}
application.yml 설정
JWT 발급자(issuer)를 검증하기 위해 Keycloak이 발급하는 정확한 iss를 확인하고 설정합니다.
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://{keycloak-hostname}/{relative-path}/realms/{your-realm}
# jwk-set-uri는 생략 가능합니다. 필요할 경우 아래처럼 명시적으로 설정 가능합니다.
# jwk-set-uri: https://{keycloak-hostname}/{relative-path}/realms/{your-realm}/protocol/openid-connect/certs
Spring Security는 issuer-uri만 설정해도 자동으로 OpenID Connect Discovery Endpoint를 통해 jwk-set-uri를 찾습니다. 다만, 특정 환경이나 네트워크 문제 발생 시 명시적으로 jwk-set-uri를 설정하는 것이 좋습니다.
리소스 서버가 certs 엔드포인트에서 수행하는 작업
리소스 서버는 {issuer-uri}/protocol/openid-connect/certs에서 JWT 서명을 검증하기 위한 공개 키(JWK)를 가져옵니다.
- JWT는 Keycloak의 개인 키로 서명됩니다.
- 리소스 서버는 Keycloak이 제공하는 공개 키를 사용하여 JWT 서명의 유효성을 검증합니다.
- 이를 통해 JWT가 신뢰할 수 있는 소스로부터 발급되었는지 확인합니다.
Keycloak JWT 발급자(issuer, iss) 확인 방법
Keycloak이 JWT의 iss 클레임을 결정할 때 사용하는 환경 변수는 다음과 같습니다.
# deployment.yml
- name: KEYCLOAK_HTTP_RELATIVE_PATH
value: "/keycloak"
- name: KEYCLOAK_HOSTNAME
value: "auth.example.com/keycloak"
- name: KEYCLOAK_PROXY
value: "edge"
- name: KEYCLOAK_PROXY_HEADERS
value: "x-forwarded"
위와 같은 환경변수를 설정한 경우, Keycloak이 발급하는 JWT의 iss 클레임 값은 다음과 같습니다.
https://auth.example.com/keycloak/realms/{realm-name}
리소스 서버는 이 값을 반드시 일치하도록 설정해야 정상적으로 JWT를 검증할 수 있습니다.
JwtAuthenticationConverter 설정
JWT에서 사용자 정보와 권한을 추출하는 컨버터를 설정합니다.
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import java.util.List;
import java.util.UUID;
public class KeyclockJwtConverter implements Converter<Jwt, AbstractAuthenticationToken> {
@Override
public AbstractAuthenticationToken convert(@NotNull Jwt jwt) {
List<GrantedAuthority> authorities = findAuthorities(jwt);
return new JwtAuthenticationToken(jwt, authorities, jwt.getSubject());
}
private List<GrantedAuthority> findAuthorities(Jwt jwt) {
... 생략
}
}
SecurityConfig 설정
JWT 검증을 위한 간단한 보안 설정을 구성합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration
public class OAuth2SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
);
http
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwtConfigurer ->
jwtConfigurer.jwtAuthenticationConverter(new KeyclockJwtConverter())
));
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
728x90
'Keycloak' 카테고리의 다른 글
[Keycloak] 어드민 API 호출 권한, 사용자에게 직접 부여하기 (0) | 2025.04.01 |
---|---|
[Keycloak] JWT에 Custom 스코프 추가하기 (0) | 2025.03.26 |
[Keycloak] Custom API (0) | 2025.03.04 |