Keycloak을 권한 서버(Authorization Server)로 사용하고, Spring Boot 애플리케이션을 OAuth2 기반 Resource Server로 구성하는 방법
JWT 및 Qpaque(불투명) 토큰 방식 모두 다룸
항목 | JWT | Opaque Token |
---|---|---|
형태 | 서명된 JSON | 식별자 문자열 (정보 없음) |
검증 | 서명 검증으로 자체 처리 | 권한 서버에 introspection(내부 검증) 요청 |
장점 | 빠르고 탈중앙화 검증 가능 | 서버 제어/철회 용이 |
단점 | 토큰 노출 위험, 만료 전 폐기 어려움 | 매 요청마다 introspection 비용 발생 |
fooClient
: JWT 토큰 기반 리소스 서버용barClient
: Opaque 토큰 기반 리소스 서버용다음의 네 가지 주요 구성 요소로 이루어짐
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Spring Security 포함됨
간단하게, Foo
라는 POJO(Plain Old Java Object)를 보호된 Resource로사용
public class Foo {
private long id;
private String name;
}
리소스에 대한 접근 수준 정의
@Configuration
public class JWTSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authz -> authz
.requestMatchers(HttpMethod.GET, "/foos/**").hasAuthority("SCOPE_read")
.requestMatchers(HttpMethod.POST, "/foos").hasAuthority("SCOPE_write")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt());
return http.build();
}
}
read
범위(scope)를 가진 액세스 토큰을 가진 사람은 누구나 Foo
객체 조회 가능Foo
객체를 생성하려면 토큰에 write
범위가 있어야 함.oauth2ResourceServer()
를 사용하여 토큰 타입이 jwt()
임을 명시Foo
객체를 조작할 수 있도록 제공하는 REST Controller
@RestController
@RequestMapping(value = "/foos")
public class FooController {
@GetMapping(value = "/{id}")
public Foo findOne(@PathVariable Long id) {
return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
}
@GetMapping
public List findAll() {
List fooList = new ArrayList();
fooList.add(new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)));
fooList.add(new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)));
fooList.add(new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)));
return fooList;
}
@PostMapping
public ResponseEntity<?> create(@RequestBody Foo foo) {
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
server:
port: 8081
servlet:
context-path: /resource-server-jwt
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8083/auth/realms/myrealm
issuer-uri
: Keycloak의 Realm에 해당하는 URI (메타데이터 자동 로딩)jwt-set-uri
로 공개키 직접 지정 가능jwk-set-uri: http://localhost:8083/auth/realms/myrealm/protocol/openid-connect/certs
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>8.19</version>
</dependency>
Foo
와 동일하게 작성public class Bar {
private long id;
private String name;
}
리소스에 대한 접근 수준 정의
@Configuration
public class OpaqueSecurityConfig {
@Value("${spring.security.oauth2.resourceserver.opaque.introspection-uri}")
String introspectionUri;
@Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-id}")
String clientId;
@Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-secret}")
String clientSecret;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authz -> authz.requestMatchers(HttpMethod.GET, "/foos/**").hasAuthority("SCOPE_read")
.requestMatchers(HttpMethod.POST, "/foos").hasAuthority("SCOPE_write")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.opaqueToken
(token -> token.introspectionUri(this.introspectionUri)
.introspectionClientCredentials(this.clientId, this.clientSecret)));
return http.build();
}
}
Foo
와 동일하게 작성oauth2ResourceServer()
를 사용하여 토큰 타입이 opaqueToken()
임을 명시server:
port: 8082
servlet:
context-path: /resource-server-opaque
spring:
security:
oauth2:
resourceserver:
opaque:
introspection-uri: http://localhost:8083/auth/realms/myrealm/protocol/openid-connect/token/introspect
introspection-client-id: barClient
introspection-client-secret: barClientSecret
introspection-uri
를 추가introspection-uri
를 통해 검증됨