이 글은 다음 가이드를 정리한 내용이다. Reactive 어플리케이션은 이 가이드를 참조해야한다. 기술할 인터페이스 및 클래스는 아래와 같다.
- ClientRegistration
- ClientRegistrationRepository
- OAuth2AuthorizedClient
- OAuth2AuthorizedClientRepository and OAuth2AuthorizedClientService
- OAuth2AuthorizedClientManager and OAuth2AuthorizedClientProvider
ClientRegistration
OIDC 1.0 제공자나 OAuth 2.0 으로 등록된 클라이언트의 표현이며 아래와 같은 내용을 담는다.
public final class ClientRegistration {
private String registrationId;
private String clientId;
private String clientSecret;
private ClientAuthenticationMethod clientAuthenticationMethod;
private AuthorizationGrantType authorizationGrantType;
private String redirectUri;
private Set<String> scopes;
private ProviderDetails providerDetails;
private String clientName;
public class ProviderDetails {
private String authorizationUri;
private String tokenUri;
private UserInfoEndpoint userInfoEndpoint;
private String jwkSetUri;
private String issuerUri;
private Map<String, Object> configurationMetadata;
public class UserInfoEndpoint {
private String uri;
private AuthenticationMethod authenticationMethod;
private String userNameAttributeName;
}
}
public static final class ClientSettings {
private boolean requireProofKey;
}
}
- registrationId: ClientRegistration 의 고유 아이디
- clientId / clientSecret: 클라이언트 아이디와 시크릿
- clientAuthenticationMethod:
- client_secret_basic
- client_secret_post
- private_key_jwt
- client_secret_jwt
- none (public clients)
- authorizationGrantType: OAuth 2.0 의 4가지 인가 타입
- authorization_code
- client_credentials
- password
- urn:ietf:params:oauth:grant-type:jwt-bearer 의 확장 타입
- redirectUri: 클라이언트가 등록한 리디렉트 URI
- scopes: openid, email, profile 과 같이 클라이언트가 인증 요청 흐름에서 요청한 범위
- clientName: 클라이언트를 보일 이름
- authorizationUri: 인가서버의 엔드포인트
- tokenUri: 인가서버의 토큰 엔드포인트
- jwkSetUri: JSON Web Key (JWK) 세트를 얻기위한 인가서버의 엔드포인트. 이는 아이디 토큰과 추가적 유저 정보 응답의 JWS 를 검증하기 위해 사용
- issuerUri: ODIC 혹은 OAUth 2.0 인가 서버의 발급자 식별자 URI
- configurationMetadata: The OpenID Provider Configuration Information. ...provider.[providerId].issuerUri 가 설정되었을 때 활용 가능함
- userInfoEndpoint.uri: 인증된 엔드 유저의 클레임/속성에 접근하기 위해 사용하는 유저 정보 엔드포인트
- userInfoEndpoint.authenticationMethod: UserInfo 엔드포인트에 어세스 토큰을 보낼 때 사용할 인증 방식
- header
- form
- query
- userNameAttributeName: 엔드 유저의 이름 혹은 식별자를 참조하는 UserInfo 응답속 속성의 이름
- requireProofKey: 이 값이 true 거나 authorizationGrantType 가 none 인 경우 PKCE 가 default 로 활성화
하나의 ClientRegistration 은 OpenID COnnect Provider 의 Configuration endpoint 혹은 인가 서버의 Metadata endpoint 로 초기화 될 수 있다.
ClientRegistrations 는 위 방식의 초기화 메소드를 제공한다.
val clientRegistration = ClientRegistrations.fromIssuerLocation("<https://idp.example.com/issuer>").build()
상기 쿼리는 아래를 순차적으로 호출한다.
- idp.example.com/issuer/.well-known/openid-configuration
- idp.example.com/.well-known/openid-configuration/issuer
- idp.example.com/.well-known/oauth-authorization-server/issuer
- stopping at the first to return a 200 response.
OpenID Connect Provider 만의 엔드포인트를 호출하기 위해선 그 대안으로 lientRegistrations.fromOidcIssuerLocation() 를 볼 수 있다.
ClientRegistrationRepository
ClientRegistration 들의 리포지토리이다.
스프링 부트 자동 설정은 spring.security.oauth2.client.registration.*[registrationId]* 이하 프로퍼티 값을 각 ClientRegistration 에 바인드하고, 각 ClientRegistration 인스턴스를 하나의 ClientRegistrationRepository 에 구성한다.
ClientRegistrationRepository 의 기본 구현체는 InMemoryClientRegistrationRepository 이다. 자동설정은 ClientRegistrationRepository 빈으로 등록한다.
OAuth2AuthorizedClient
인가된 클라이언트의 표현이다. OAuth2AuthorizedClient 는 OAuth2AccessToken (and optional OAuth2RefreshToken) 와 ClientRegistration (client) 및 이를 허가한 Principal 된 엔드유저인 리소스 소유자와 연관짓는 것을 담당한다.
OAuth2AuthorizedClientRepository and OAuth2AuthorizedClientService
OAuth2AuthorizedClientRepository 는 웹 요청사이 OAuth2AuthorizedClient(s) 들을 영속할 책임을 갖는다. 한편 OAuth2AuthorizedClientService는 어플리케이션 레벨에서 OAuth2AuthorizedClient(s) 들을 관리한다.
개발자 관점에서 OAuth2AuthorizedClientRepository or OAuth2AuthorizedClientService 는 클라이언트와 연관된 OAuth2AccessToken 를 룩업할 능력을 제공한다. 이는 보호된 리소스 요청을 초기화할 때 사용될 수 있다.
@Controller
class OAuth2ClientController {
@Autowired
private lateinit var authorizedClientService: OAuth2AuthorizedClientService
@GetMapping("/")
fun index(authentication: Authentication): String {
val authorizedClient: OAuth2AuthorizedClient =
this.authorizedClientService.loadAuthorizedClient("okta", authentication.getName());
val accessToken = authorizedClient.accessToken
...
return "index";
}
}
스프링 자동설정은 이들을 빈으로 등록한다. 그러나 어플리케이션이 이들의 오버라이드와 등록을 결정할 수 있다.
OAuth2AuthorizedClientService 의 기본 구현체는 InMemoryOAuth2AuthorizedClientService 이며 OAuth2AuthorizedClient 를 인 메모리에 저장한다.
다르게, JdbcOAuth2AuthorizedClientService 를 사용하여 OAuth2AuthorizedClient 를 디비에 저장할 수 있다. 이는 OAuth 2.0 Client Schema 에 서술된 테이블 정의에 의존한다.
OAuth2AuthorizedClientManager and OAuth2AuthorizedClientProvider
OAuth2AuthorizedClientManager 는 OAuth2AuthorizedClient 들의 전체적인 관리를 담당한다.
우선적인 책임은 다음을 포함한다.
- OAuth2AuthorizedClientProvider 를 사용하여, OAuth 2.0 Client 를 인가 혹은 재인가
- OAuth2AuthorizedClient 의 영속화를 위임한다. 일반적으로 OAuth2AuthorizedClientService 혹은 OAuth2AuthorizedClientRepository 를 활용한다.
- OAuth 2.0 Client 의 인가 혹은 재인가가 성공 혹은 실패 시 요청을 아래로 위임한다.
- 성공 시: OAuth2AuthorizationSuccessHandler
- 실패 시: OAuth2AuthorizationFailureHandler
OAuth2AuthorizedClientProvider 는 OAuth 2.0 Client 를 인가 혹은 재인가하기 위한 전략을 구현한다. (authorization grant type such as authorization_code, client_credentials, and others.)
OAuth2AuthorizedClientManager 의 기본 구현체는 DefaultOAuth2AuthorizedClientManager 이다. 이는 위임 베이스 구성을 통해 다중 인가 타입을 지원할 수 있는 OAuth2AuthorizedClientProvider 와 연관된다. OAuth2AuthorizedClientProviderBuilder 를 통해 위임 베이스 구성을 설정하고 빌드할 수 있다.
이하 예제는 OAuth2AuthorizedClientProvider 를 설정하고 빌드하는 예시이다. 이하에서는 authorization_code, refresh_token, client_credentials, and password 인가 그랜트 타입을 구성한다.
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
인가 시도가 성공한 경우:
- DefaultOAuth2AuthorizedClientManager 가 OAuth2AuthorizationSuccessHandler 로 요청을 위임한다.
- 이는 기본적으로 OAuth2AuthorizedClientRepository 통해 OAuth2AuthorizedClient 를 저장한다.
재인가 시도가 실패한 경우:
- 기존에 저장한 OAuth2AuthorizedClient 가 OAuth2AuthorizedClientRepository 에서 삭제된다.
- 이는 RemoveAuthorizedClientOAuth2AuthorizationFailureHandler 에서 이루어진다.
이러한 동작은 이하 setter 에서 사용자화할 수 있다.
- setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)
- setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)
DefaultOAuth2AuthorizedClientManager는 또한 Function<OAuth2AuthorizeRequest, Map<String, Object>> 유형의 contextAttributesMapper와 연결되며, 이는 OAuth2AuthorizeRequest의 속성을 OAuth2AuthorizationContext에 연결할 속성 맵에 매핑하는 일을 담당한다. 이 기능은 OAuth2AuhorizedClientProvider에 필수(지원되는) 속성을 제공해야 할 때 유용하다. (예: PasswordOAuth2AuthorizedClientProvider는 리소스 소유자의 사용자 이름과 비밀번호가 OAuth2AuthorizationContext.getAttributes()에서 사용 가능해야한다).
이하 예시는 contextAttributesMapper 의 예시를 보인다.
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.password()
.refreshToken()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
return authorizedClientManager
}
private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableMap<String, Any>> {
return Function { authorizeRequest ->
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
val servletRequest: HttpServletRequest = authorizeRequest.getAttribute(HttpServletRequest::class.java.name)
val username: String = servletRequest.getParameter(OAuth2ParameterNames.USERNAME)
val password: String = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD)
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = hashMapOf()
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
}
contextAttributes
}
}
DefaultOAuth2AuthorizedClientManager 는 HttpServletRequest 의 맥락에서 사용되도록 설계되었다. HttpServletRequest context 밖에서 조작해야할 경우 AuthorizedClientServiceOAuth2AuthorizedClientManager 를 사용하라.
서비스 어플리케이션은 AuthorizedClientServiceOAuth2AuthorizedClientManager 를 사용하는 일반적인 유즈케이스이다. 서비스 어플리케이션들은 유저 상호작용 없이, 곧잘 백그라운드에서 실행되며 일반적으로 사용자 계정이 아닌 시스템 레벨 계정에서 실행된다. 이 때 OAuth 2.0 Client 는 client_credentials 을 서비스 어플리케이션의 인가 타입으로 설정할 수 있다.
이하 예시는 client_credentials 를 제공하는 AuthorizedClientServiceOAuth2AuthorizedClientManager 설정 예시이다.
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientService: OAuth2AuthorizedClientService): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build()
val authorizedClientManager = AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
'개발공부 > 주저리' 카테고리의 다른 글
| 믹스인(Mixin)의 정의와 예시: 코틀린 믹스인 구현의 예 (0) | 2025.05.29 |
|---|---|
| K8S 컨테이너 런타임에 대한 잡지식 (1) | 2024.09.12 |
| 모름을 모른다는 것 (1) | 2024.04.10 |
| Enter passpharase for key: 매번 입력하지 않도록 설정하기 (0) | 2024.04.07 |
| 코드를 어떻게 비판하나 (0) | 2021.10.20 |