백엔드 애플리케이션 간 통신을 구현해야 하는 경우가 종종 있다. 특히 여러 서비스를 포함하는 백엔드 애플리케이션에서는 이러한 통신이 필수적이다. 이와 같은 상황에서 시스템에 OAuth 2 기반의 인증 및 권한 부여가 적용되어 있다면, 동일한 방식을 사용하여 애플리케이션 간 호출을 인증하는 것이 권장된다. 일부 경우에는 간단함을 위해 HTTP Basic 및 API 키 인증 방법을 사용하는 개발자도 있지만, 시스템을 더 일관되고 안전하게 유지하려면 OAuth 2 클라이언트 자격 증명 승인 타입 (Client Credentials Grant Type)을 사용하는 것이 선호된다.
1. OAuth 2 로그인 구현
Spring Boot를 사용하면 표준 사례에서, 인증 서버가 OAuth 2 및 OpenID Connect 사양을 올바르게 준수하는 경우 인증 구성을 매우 간단하게 할 수 있다.
1.1 공통 공급자를 사용한 인증 구현
로그인 기능을 가진 간단한 Spring 웹 애플리케이션을 구현하기 위해 프로젝트에 몇 가지 리소스를 추가해야 한다. 예제 1은 애플리케이션 구현에 필요한 의존성을 보여준다.
▼ 예제 1. Dependencies needed for our demonstration
<dependency>
<groupId>org.springframework.boot</groupId>
<!-- The only new dependency you observe is the OAuth 2 client dependency. -->
<!-- We need this dependency for all the OAuth 2 client capabilities -->
<!-- we configure in the project.-->
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
로그인 기능 구현을 위한 간단한 컨트롤러를 추가한다. 이 애플리케이션은 홈페이지만 가지고 있다.
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "index.html";
}
}
인증 완료 후 화면에 표시될 HTML 페이지를 추가한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Home</h1>
</body>
</html>
예제 2는 웹 애플리케이션의 인증 방식으로 OAuth 2 로그인을 구성하는 내용을 보여준다. 이렇게 애플리케이션을 구성하면 인증 코드 부여 타입 (Authorization Code Grant Type)을 자동으로 따르게 된다. 사용자를 특정 인증 서버로 로그인하도록 리디렉션한 뒤, 인증이 성공하면 다시 애플리케이션으로 리디렉션한다.
▼ 예제 2. Configuring the OAuth 2 login
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
http.oauth2Login(Customizer.withDefaults());
http.authorizeHttpRequests(
c -> c.anyRequest().authenticated());
return http.build();
}
}
애플리케이션을 구동하려면 인증 URL, 토큰 URL, 클라이언트 ID, 클라이언트 시크릿 등 모든 세부 정보가 필요하다. 하지만 다행히도 Spring Security가 이를 간소화해 줄 수 있다. 애플리케이션이 Spring Security에서 잘 알려진 Provider로 간주하는 제공자 중 하나를 사용하는 경우, 이러한 세부 정보 대부분은 미리 채워져 있다 (잘 알려진 제공자들의 정보는 디폴트로 구성되어 있음). 사용자는 애플리케이션의 클라이언트 자격 증명만 구성하면 된다.
Spring Security가 잘 알려진 제공자로 간주하는 목록은 다음과 같다.
- GitHub
- Okta
Spring Security는 CommonOAuth2Provider 클래스를 통해 이러한 제공자에 대한 세부 정보를 미리 구성한다. 따라서 이 제공자 중 하나를 사용하는 경우, 애플리케이션 속성에서 클라이언트 자격 증명만 설정하면 된다. 다음 코드 스니펫은 Google을 사용할 때 클라이언트 ID와 클라이언트 시크릿을 구성하기 위해 필요한 두 가지 속성을 보여준다.
spring.security.oauth2.client.registration.google.client-id=xxx...
spring.security.oauth2.client.registration.google.client-secret=yyy...
이미 Google Developer Console에 애플리케이션을 등록했다고 가정한다.
1.2 사용자에게 더 많은 가능성 제공
인터넷을 충분히 사용해 보았다면, 많은 애플리케이션이 사용자가 로그인할 수 있는 여러 방법을 제공한다는 것을 관찰했을 것이다. 때로는 애플리케이션에 로그인하기 위해 네다섯 가지 제공자 중에서 선택할 수 있다. 이러한 접근 방식은 유용하다. 모든 사용자가 이미 소셜 네트워크 계정을 가지고 있는 것은 아니기 때문이다. 어떤 사람들은 Facebook 계정을 가지고 있지만, 다른 사람들은 LinkedIn을 선호한다. 일부 개발자는 GitHub 계정을 사용하여 로그인하는 것을 선호하지만, 다른 사람들은 Gmail 주소를 사용한다.
Spring Security를 사용하면 여러 Provider를 쉽게 구현할 수 있다. 예를 들어, 애플리케이션 사용자가 Google 또는 GitHub을 사용하여 로그인할 수 있도록 하려면 두 제공자에 대해 자격 증명을 비슷하게 구성하기만 하면 된다. 다음 단편 조각은 GitHub을 인증 방법으로 추가하기 위해 application.properties 파일에서 필요한 속성을 보여준다. 1.1에서 Google에 대해 이미 구성한 속성을 유지해야 한다는 것을 기억하라.
다른 제공자와 마찬가지로, 먼저 애플리케이션을 등록하고 클라이언트 ID와 시크릿을 application.properties 파일에 구성해야 한다. 애플리케이션 등록 방법은 제공자마다 다르다.
애플리케이션은 인증을 요청하기 전에 두 가지 로그인 옵션(이전에 구성한 옵션)을 제공한다. 사용자는 Google 또는 GitHub 중 하나를 선택하여 로그인해야 한다. 선호하는 제공자를 선택하면 애플리케이션은 해당 제공자의 특정 인증 페이지로 리디렉션한다.
1.3 사용자 정의 인증 서버 사용
Spring Security는 네 가지 공통 제공자 목록을 정의한다. 하지만 공통 제공자 목록에 없는 제공자를 사용하고 싶다면, 예를 들어 LinkedIn, Twitter, Yahoo 등과 같은 다른 대안을 사용하거나 직접 구축한 커스텀 인증 서버를 사용하고 싶을 수도 있다.
어떤 제공자와도, 심지어 직접 구축한 커스텀 제공자와도 OAuth 2 로그인을 구성할 수 있다. 여기서 중요한 것은 클라이언트 구성을 올바르게 보장하는 것이다. 예제 2는 인증 서버에 구성된 등록 클라이언트를 보여준다. 이때 가장 중요한 점은 리디렉션 URI가 로그인 구현 대상 애플리케이션에 대해 예상하는 URI와 일치하는지 확인하는 것이다.
http://localhost:8080/login/oauth2/code/my_authorization_server
리디렉션 URI의 구조를 분석해 보자. 표준 리디렉션 URI는 /login/oauth2/code 경로 뒤에 OAuth 서버의 이름이 오는 형식을 사용한다. 이 예제에서는 OAuth 서버에 부여한 이름이 my_authorization_server이므로, 리디렉션 URI는 다음과 같은 형식이 된다.
/login/oauth2/code/my_authorization_server
이 구조에서 my_authorization_server는 OAuth 인증 서버를 식별하는 이름으로 사용된다.
예제 3은 클라이언트 details를 등록하는 OAuth 서버의 구성 부분을 보여준다.
▼ 예제 3. The client details registered on the authorization server side
@Bean
public RegisteredClientRepository registeredClientRepository() {
var registeredClient = RegisteredClient
.withId(UUID.randomUUID().toString())
.clientId("client")
.clientSecret("secret")
.clientAuthenticationMethod(
ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(
AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri(
"http://localhost:8080/login/oauth2/code/my_authorization_server")
.scope(OidcScopes.OPENID)
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
기억해야 할 점은 동일한 시스템에서 두 개의 애플리케이션이 동일한 포트 번호로 시작할 수 없다는 것이다. 웹 애플리케이션이 포트 8080을 사용하고 있기 때문에, OAuth 서버의 포트를 다른 번호로 변경해야 한다. 다음 코드 스니펫에서 보이는 것처럼, 이 예제에서는 포트 7070을 선택하여 application.properties 파일에 구성했다.
server.port=7070
이렇게 설정하면 OAuth 서버는 포트 7070에서 실행되고, 웹 애플리케이션은 포트 8080에서 실행된다.
이제 웹 애플리케이션의 구성으로 넘어갈 수 있다. 이전 예제에서 공통 제공자를 사용했기 때문에 따로 정의할 필요가 없었다. Spring Security는 공통 제공자에 필요한 모든 세부 정보를 이미 알고 있다. 그러나 다른 제공자를 사용하려면 몇 가지 구성을 추가로 해야 한다. Spring Security는 다음 사항을 알아야 한다.
- authorization code flow 동안 사용자를 리디렉션할 위치를 제공자에게 알려주는 authorization 엔드포인트
- 액세스 토큰을 얻기 위해 애플리케이션이 호출해야 할 token 엔드포인트
- 액세스 토큰을 검증하기 위해 애플리케이션이 필요로 하는 key set 엔드포인트
좋은 소식은, Provider(OAuth 서버)가 OpenID Connect 프로토콜을 올바르게 구현했다면, 발급자 URI (Issuer URI)만 구성하면 된다는 점이다. 애플리케이션은 발급자 URI를 사용하여 authorization, token, key set URI와 같은 필요한 모든 세부 정보를 찾는다. 만약 OAuth 서버가 OpenID Connect 프로토콜을 준수하지 않는다면, 이러한 세 가지 세부 정보를 application.properties 파일에 명시적으로 구성해야 한다.
이전에 구축한 OAuth 서버는 OpenID Connect 프로토콜을 올바르게 구현했기 때문에 발급자 URI에 의존할 수 있다. 다음 코드 스니펫은 발급자 URI를 구성하는 방법을 보여준다. 제공자에 이름을 지정한 것을 확인할 수 있다. 이 예제에서는 my_authorization_server라는 이름으로 식별했지만, 제공자를 식별하기 위해 원하는 이름을 선택할 수 있다.
spring.security.oauth2.client.provider.my_authorization_server.issuer-uri=http://127.0.0.1:7070
우리는 OAuth 서버와 사용할 웹 애플리케이션 모두를 로컬 시스템에서 실행한다. 동일한 시스템에서 이러한 애플리케이션을 실행하고 브라우저에서 접근하면, 브라우저가 사용자 세션을 저장하는 데 사용하는 쿠키와 관련하여 문제가 발생할 수 있다. 이 때문에, 한 애플리케이션에는 IP 주소 "127.0.0.1"을 사용하고, 다른 애플리케이션에는 DNS 이름 "localhost"를 사용하는 것을 권장한다. 네트워킹 관점에서 이 둘은 동일하며 동일한 시스템(로컬 시스템)을 가리키지만, 브라우저는 이를 서로 다른 것으로 간주하여 세션을 올바르게 관리할 수 있다. 이 예제에서는 OAuth 서버를 127.0.0.1로 참조하고, 웹 애플리케이션에는 localhost를 사용한다.
예제 4는 클라이언트 등록 구성을 보여준다. 제공자가 누구인지 선언하는 것 외에도, 클라이언트 등록은 이전에 공통 제공자를 사용할 때 작성했던 것보다 약간 더 길어진다. 클라이언트 ID와 클라이언트 시크릿 외에도 다음 항목을 작성해야 한다.
- 제공자 이름: 공통 제공자가 아닌 경우 사용하려는 제공자에게 부여하는 이름이다.
- 클라이언트 인증 방식: 애플리케이션이 제공자의 보안 엔드포인트를 호출하기 위한 인증 방식(일반적으로 HTTP Basic)이다.
- 리디렉션 URI: 인증이 올바르게 완료된 후 제공자가 사용자를 리디렉션할 것으로 예상되는 URI이다. 이 URI는 인증 서버 측에 등록된 URI 중 하나와 일치해야 한다.
- 웹 애플리케이션이 요청하는 스코프: 웹 애플리케이션이 요청하는 스코프는 인증 서버 측에 등록된 스코프 중 하나여야 한다.
▼ 예제 4. The client registration configuration
# The client ID registered at the authorization server side
spring.security.oauth2.client.registration.my_authorization_server.client-id=client
# The display name of the client
spring.security.oauth2.client.registration.my_authorization_server.client-name=Custom
# The client secret registered at the authorization server side
Spring.security.oauth2.client.registration.my_authorization_server.client-secret=secret
# The name of the custom provider
spring.security.oauth2.client.registration.my_authorization_server.provider=my_authorization_server
# The app’s authentication method to call the provider’s protected endpoints
spring.security.oauth2.client.registration.my_authorization_server.client-authentication-method=
client_secret_basic
# The URI the provider redirects the user to after successful authentication
spring.security.oauth2.client.registration.my_authorization_server.redirect-uri=
➥http://localhost:8080/login/oauth2/code/my_authorization_server
# The scope the app requests
spring.security.oauth2.client.registration.my_authorization_server.scope[0]=openid
OAuth 서버와 웹 애플리케이션을 시작할 수 있다. 먼저 OAuth 서버를 시작해야 한다. 웹 애플리케이션이 시작되면, 발급자 URI(Issuer URI)를 호출하여 필요한 나머지 세부 정보를 가져온다. 두 애플리케이션을 모두 시작한 후, 웹 애플리케이션에 브라우저를 사용해 http://localhost:8080 주소로 접근하라. 사용자 인증을 위해 선택할 수 있는 목록에 커스텀 제공자가 표시된 것을 확인할 수 있을 것이다.
1.4 구성에 유연성 추가
Spring Boot의 기본 설정 방식은 많은 경우에 충분하지만, 특정 상황에서는 properties 파일에 의존하지 않고 동적으로 구성할 필요가 있을 수 있다. 예를 들어, 자격 증명을 변경하거나 제공자의 활성화 및 비활성화를 동적으로 처리하려는 경우, properties 파일만으로는 이를 처리하기 어렵다.
이때 두 가지 중요한 타입이 있다.
- ClientRegistration: OAuth2 인증을 위한 클라이언트의 자격 증명, 리디렉션 URI, 인증 URI 등 필요한 모든 세부 정보를 정의하는 객체이다. 클라이언트가 인증 서버와 통신하기 위한 기본적인 정보를 담고 있다.
- ClientRegistrationRepository: 이 인터페이스는 클라이언트 등록 정보를 저장하고 검색하는 메커니즘을 정의한다. 예를 들어, 클라이언트 등록 정보를 데이터베이스나 비밀 관리 시스템(Vault)에서 읽어오는 방식으로 구현할 수 있다.
이러한 방식으로, 애플리케이션이 실행되는 동안 클라이언트 등록 정보를 동적으로 수정하거나 추가할 수 있으며, 애플리케이션을 다시 배포하지 않고도 변경된 자격 증명이나 다른 세부 정보를 반영할 수 있다.
이 예제에서는 application.properties 파일을 사용하여 클라이언트 자격 증명을 주입하고, 이를 ClientRegistrationRepository 인터페이스를 통해 관리하는 방식입니다. 이 방식은 클라이언트 등록 정보를 메모리 내에 저장하는 예제이지만, 동일한 접근 방식을 사용하여 데이터베이스나 다른 저장소에서 클라이언트 정보를 가져올 수 있습니다.
먼저, application.properties 파일에 필요한 클라이언트 자격 증명을 설정하고, 이를 ClientRegistration 객체로 변환한 후, ClientRegistrationRepository 인터페이스의 메모리 내 구현을 사용하여 클라이언트 등록 정보를 관리합니다.
이 예제는 간단하게 유지한다. 여전히 application.properties 파일을 사용하지만, properties의 이름을 변경하여 Spring Boot가 더 이상 구성을 처리하지 않도록 한다. 하지만 이 간단한 예제는 데이터를 데이터베이스에 저장하거나 특정 엔드포인트를 호출하여 가져오고자 할 때 사용할 동일한 접근 방식을 보여준다. 이러한 모든 경우에, 반드시 ClientRegistrationRepository 인터페이스를 적절히 구현해야 한다.
ClientRegistrationRepository 컴포넌트를 Spring 빈으로 정의한다. 애플리케이션은 구현한 이 컴포넌트를 사용하여 클라이언트 등록 정보를 가져온다. 예제 5는 메모리 내 구현(in-memory implementation)을 사용한 예제를 보여준다. 이 예제에서 나는 다음 세 가지 작업을 수행한다:
- properties 파일에서 자격 증명 값을 주입한다.
- 필요한 모든 세부 정보로 ClientRegistration 객체를 생성한다.
- 이를 메모리 내 ClientRegistrationRepository 구현에 구성한다.
▼ 예제 5. Implementing custom logic
@Configuration
public class SecurityConfig {
// Injecting credentials values from the properties file
@Value("${client-id}")
private String clientId;
@Value("${client-secret}")
private String clientSecret;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
http.oauth2Login(Customizer.withDefaults());
http.authorizeHttpRequests(
c -> c.anyRequest().authenticated()
);
return http.build();
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(
// Providing an in-memory repository implementation that contains the
this.googleClientRegistration());
}
private ClientRegistration googleClientRegistration() {
// Creating the client registration based on the template of the
// common provider Google
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId(clientId)
.clientSecret(clientSecret)
.build();
}
}
1.5 OAuth 2 로그인에 대한 인증 관리
이 섹션에서는 인증 세부 정보를 사용하는 방법에 대해 다룬다. 대부분의 경우, 애플리케이션은 누가 로그인했는지 알아야 한다. 이는 화면을 다르게 표시하거나 다양한 권한 제한을 적용하기 위한 요구 사항이다. 다행히도, oauth2Login() 인증 방식을 사용하는 것은 다른 인증 방식과 다르지 않다.
Spring Security의 인증 설계는 인증이 성공하면 애플리케이션은 항상 인증 세부 정보를 시큐리티 컨텍스트에 추가하는 것으로 끝난다. oauth2Login()을 사용하는 것도 예외는 아니다.
인증 세부 정보(Authentication details)가 시큐리티 컨텍스트에 있다는 것을 알면, 이를 이전에 논의한 인증 방식들(httpBasic(), formLogin(), oauth2ResourceServer())과 동일한 방식으로 사용할 수 있다:
- Authentication 객체를 메서드 파라미터로 주입할 수 있다.
- 애플리케이션 어디에서나 시큐리티 컨텍스트에서 이를 가져올 수 있다. (SecurityContextHolder.getContext().getAuthentication())
- Pre/Post 애노테이션 (pre-/post-annotations)을 사용할 수 있다.
Authentication 인터페이스를 사용하여 User 이름이나 authorization과 같은 표준 사용자 세부 정보를 얻을 수 있다. 커스텀 세부 정보가 필요한 경우, 예.6에서 설명한 것처럼 OAuth2AuthenticationToken 인터페이스의 구현을 직접 사용할 수 있다. OAuth 2의 경우, OAuth2AuthenticationPrincipal 클래스가 인터페이스 구현을 정의한다. 그러나 유지보수성을 위해 가능한 한 모든 곳에서 Authentication 인터페이스를 사용할 것을 권장하며, 인터페이스 참조를 사용해 얻을 수 없는 세부 정보가 필요한 경우에만 구현을 사용하는 것이 좋다.
▼ 예제 6. Obtaining the authentication details
@Controller
public class HomeController {
@GetMapping("/")
public String home(OAuth2AuthenticationToken authentication) {
// do something with the authentication
return "index.html";
}
}
2. OAuth 2 클라이언트 구현
서비스를 OAuth 2 클라이언트로 구현하는 방법을 다룬다. 서비스 지향 시스템에서는 애플리케이션 간 통신이 자주 발생하며, 이러한 경우 다른 애플리케이션에 요청을 보내는 애플리케이션은 클라이언트가 된다. 대부분의 경우, OAuth 2를 통해 요청 인증을 구현하기로 결정하면, 애플리케이션은 클라이언트 자격 증명 부여 방식(Client Credentials Grant Type)을 사용하여 액세스 토큰을 얻는다.
클라이언트 자격 증명 부여 방식은 사용자가 포함되지 않으므로 리디렉션 URI와 권한 부여 URI가 필요 없다. 클라이언트 자격 증명만으로 클라이언트가 인증을 받고, 토큰 URI에 요청을 보내 액세스 토큰을 얻을 수 있다.
Spring Security로 OAuth 2 클라이언트 기능을 구현하는 방법을 보여준다. 여기서는 클라이언트 자격 증명 부여 방식을 사용하여 OAuth 서버에서 액세스 토큰을 얻는 애플리케이션을 만든다. 이 애플리케이션은 OAuth 서버에서 액세스 토큰을 받아온다. 예제는 액세스 토큰을 가져오는 과정만 다루며, 요청을 생성하는 방식은 다루지 않는다. 액세스 토큰을 얻는 방법을 알면, 어떤 기술을 사용하든 HTTP 요청 헤더에 쉽게 추가할 수 있다. (액세스 토큰 값을 "Bearer" 문자열과 함께 Authorization 헤더에 추가하는 방식이다.)
따라서 이 예제의 핵심은 클라이언트 자격 증명 부여 방식을 사용해 OAuth 2 인증 서버로부터 액세스 토큰을 받아오는 애플리케이션을 구성하는 것이다. 액세스 토큰을 제대로 가져왔음을 확인하기 위해 데모 엔드포인트의 응답 본문에 액세스 토큰을 반환한다.
- 사용자가 /token이라는 데모 엔드포인트를 cURL(또는 Postman과 같은 도구)을 사용해 호출한다.
- 애플리케이션을 시뮬레이션하는 도구(cURL)가 예제를 위해 만든 애플리케이션에 요청을 보낸다.
- 애플리케이션은 클라이언트 자격 증명 부여 방식을 사용하여 OAuth 서버에서 액세스 토큰을 얻는다.
- 애플리케이션은 액세스 토큰 값을 HTTP 응답 본문에 포함시켜 클라이언트에게 반환한다. 사용자는 HTTP 응답 본문에서 액세스 토큰 값을 확인할 수 있다.
▼ 예제 7. The client details registered on the authorization server side
@Bean
public RegisteredClientRepository registeredClientRepository() {
var registeredClient = RegisteredClient
.withId(UUID.randomUUID().toString())
.clientId("client")
.clientSecret("secret")
.clientAuthenticationMethod(
ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(
// Adding a client registration that allows the use of
// the client credentials grant type
AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(OidcScopes.OPENID)
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
다른 인증 방식과 마찬가지로, Spring Security는 애플리케이션을 OAuth 2 클라이언트로 설정할 수 있는 HttpSecurity 객체의 메서드를 제공한다. 애플리케이션을 OAuth 2 클라이언트로 설정하려면, 다음 리스트에서 제공된 oauth2Client() 메서드를 호출하면 된다.
▼ 예제 8. Configuring OAuth 2 client authentication
@Configuration
public class ProjectConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
// Using the oauth2Client() authentication method makes
// this app an OAuth 2 client.
http.oauth2Client(Customizer.withDefaults());
http.authorizeHttpRequests(
c -> c.anyRequest().permitAll()
);
return http.build();
}
}
애플리케이션은 액세스 토큰 요청을 OAuth 서버에 보내기 위해 필요한 몇 가지 세부 정보를 알아야 한다. 이러한 세부 정보는 ClientRegistrationRepository 컴포넌트를 통해 제공된다.
하지만 공통 제공자를 사용하지 않았기 때문에, 스코프(scope), 토큰 URI, 인증 방식과 같은 추가 세부 정보를 지정해야 했다. 또한 클라이언트 자격 증명(Client Credentials)을 부여 방식(Grant Type)으로 구성한 것도 확인할 수 있다.
▼ 예제 9. Configuring the client registration details for the client app
@Configuration
public class ProjectConfig {
// Omitted code
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
ClientRegistration c1 =
ClientRegistration.withRegistrationId("1")
.clientId("client")
.clientSecret("secret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientAuthenticationMethod(
ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.tokenUri("http://localhost:7070/oauth2/token")
.scope(OidcScopes.OPENID)
.build();
var repository =
new InMemoryClientRegistrationRepository(c1);
return repository;
}
}
클라이언트 관리자 (Client Manager) 컴포넌트는 액세스 토큰을 얻기 위한 요청을 처리한다.
컨트롤러는 클라이언트 관리자를 사용하여 OAuth 서버에서 액세스 토큰을 가져온다. 클라이언트 관리자는 OAuth 서버에 연결하고, 부여 방식을 적절히 사용하여 액세스 토큰을 가져오는 역할을 담당하는 Spring Security 컴포넌트다.
OAuth2AuthorizedClientManager 클래스는 클라이언트 관리자를 정의한다.
▼ 예제 10. Implementing an OAuth 2 client manager
@Configuration
public class ProjectConfig {
// Omitted code
@Bean
public OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository auth2AuthorizedClientRepository) {
// 사용하려는 grant 방식을 지정하기 위해 provider 객체를 생성합니다.
var provider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
// 클라이언트 Request 로직을 처리할 클라이언트 Manager 인스턴스 생성.
var cm = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository,
auth2AuthorizedClientRepository);
// 클라이언트 Manager에 Provider 설정.
cm.setAuthorizedClientProvider(provider);
return cm;
}
}
이제 액세스 토큰이 필요한 곳에서 클라이언트 매니저를 사용할 수 있다. 이 예제에서는 클라이언트 매니저를 컨트롤러에서 직접 사용하여 예제를 단순화하고 OAuth 2 클라이언트 구현에 대한 논의에 집중할 수 있도록 했다. 실제 애플리케이션은 더 복잡할 수 있으며, 객체의 책임을 올바르게 분리한 설계에서는 클라이언트 매니저가 컨트롤러에 의해 직접 사용되지 않고, 프록시 객체에 의해 사용될 가능성이 크다.
예제 11은 클라이언트 매니저 인스턴스를 주입하는 방법을 보여주며, 엔드포인트를 사용하여 액세스 토큰을 가져오는 과정을 시연한다. 애플리케이션에서 노출한 /token 엔드포인트를 호출하면, 응답 본문에 액세스 토큰 값이 포함되어 있어야 한다.
▼ 예제 11. Using the OAuth 2 client manager to get a token
@RestController
public class DemoController {
private final OAuth2AuthorizedClientManager clientManager;
// Omitted constructor
// Exposing a GET endpoint at the /token path
@GetMapping("/token")
public String token() {
// Creating an authorization request instance
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest
.withClientRegistrationId("1")
.principal("client")
.build();
// Sending the request, the app returns the access token value
var client =
clientManager.authorize(request);
// The app returns the access token value in the response body.
return client
.getAccessToken().getTokenValue();
}
}
다음 cURL 명령을 사용하여 애플리케이션이 노출한 엔드포인트를 호출해야 한다.
curl http://localhost:8080/token
응답 본문에는 액세스 토큰 값이 포함되어 있어야 하며, 다음과 유사한 형태가 될 것이다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Im
JpbGwiLCJpYXQiOjE1MTYyMzkwMjJ9.zjL2JXw0TVgNgTMUKmP0-PTPklULUVmV_5re50eZoHw
'마이크로서비스 아키텍처' 카테고리의 다른 글
OAuth 2 인증 서버 구현 (0) | 2024.12.22 |
---|---|
Vault란 무엇인가? (0) | 2024.12.11 |