스프링부트 2.X는 OAuth 2.0 로그인을 완전히 자동화한다.
spring:
security:
oauth2:
client:
registration:
google:
client-id: google-client-id
client-secret: google-client-secret
| Spring Boot 2.x | ClientRegistration |
|---|---|
| spring.security.oauth2.client.registration.[registrationId] | registrationId |
| spring.security.oauth2.client.registration.[registrationId].client-id | clientId |
| spring.security.oauth2.client.registration.[registrationId].client-secret | clientSecret |
| spring.security.oauth2.client.registration.[registrationId].client-authentication-method | clientAuthenticationMethod |
| spring.security.oauth2.client.registration.[registrationId].authorization-grant-type | authorizationGrantType |
| spring.security.oauth2.client.registration.[registrationId].redirect-uri | redirectUri |
| spring.security.oauth2.client.registration.[registrationId].scope | scopes |
| spring.security.oauth2.client.registration.[registrationId].client-name | clientName |
| spring.security.oauth2.client.provider.[providerId].authorization-uri | providerDetails.authorizationUri |
| spring.security.oauth2.client.provider.[providerId].token-uri | providerDetails.tokenUri |
| spring.security.oauth2.client.provider.[providerId].jwk-set-uri | providerDetails.jwkSetUri |
| spring.security.oauth2.client.provider.[providerId].issuer-uri | providerDetails.issuerUri |
| spring.security.oauth2.client.provider.[providerId].user-info-uri | providerDetails.userInfoEndpoint.uri |
| spring.security.oauth2.client.provider.[providerId].user-info-authentication-method | providerDetails.userInfoEndpoint.authenticationMethod |
| spring.security.oauth2.client.provider.[providerId].user-name-attribute | providerDetails.userInfoEndpoint.userNameAttributeName |
CommonOAuth2Provder엔 유명한 provider인 구글, 깃허브 페이스북 등의 전용 디폴트 클라이언트 프로퍼티가 정의되어 있다.
authorization-uri나 token-uri, user-info-uri는 자주 변경되는 값이 아니기에 디폴트 값을 제공해서 필요한 설정을 줄이는 게 좋다.client-id, client-secret만 있으면 된다.OAuth2ClientAutoConfiguration이다.
ClientRegistration을 가지고 있는 ClientRegistrationRepository를 @Bean으로 등록한다.SecurityFilterChain을 @Bean으로 등록하고 Oauth 2.0 로그인을 httpSecurity.oauth2Login()으로 가능하게 한다.@Configuration
public class OAuth2LoginConfig {
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
}
private ClientRegistration googleClientRegistration() {
return ClientRegistration.withRegistrationId("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email", "address", "phone")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build();
}
}
SecurityFilterChain 등록 방법@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2Login(withDefaults());
return http.build();
}
}
@Configuration
public class OAuth2LoginConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2Login(withDefaults());
return http.build();
}
@Bean가
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
}
private ClientRegistration googleClientRegistration() {
return ClientRegistration.withRegistrationId("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email", "address", "phone")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build();
}
}
HttpSecurity.oauth2Login()은 OAuth 2.0 로그인을 커스터마이징 하는 많은 설정을 제공한다.
oauth2Login().authorizationEndpoint() - 인가 엔드 포인트 설정ex) oauth2Login().tokenEndpoint() - 토큰 엔드 포인트 설정
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(authorization -> authorization
...
)
.redirectionEndpoint(redirection -> redirection
...
)
.tokenEndpoint(token -> token
...
)
.userInfoEndpoint(userInfo -> userInfo
...
)
);
return http.build();
}
}
DefaultLoginPageGenerationFilter가 생성해준다.ClientRegistration.clientName을 보여준다.
oauth2Login.loginPage()를 사용한다.
/login/oauth2로 로그인 페이지를 제공하는 @Controller가 있어야 한다.oauth2Login().authorizationEndpoint().baseUri()도 설정 가능
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.loginPage("/login/oauth2")
...
.authorizationEndpoint(authorization -> authorization
.baseUri("/login/oauth2/authorization")
...
)
);
return http.build();
}
}
baseUri()를 변경하는 것은 선택사항이고 변경했다면 각 OAuth 클라이언트 링크와 baseUri()가 일치해야 한다.
<a href="/login/oauth2/authorization/google">Google</a>
baseUri는 /login/oauth2/code/*이다.
OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI에 정의되어 있다.아래와 같이 커스터마이징할 수도 있다.
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.redirectionEndpoint(redirection -> redirection
.baseUri("/login/oauth2/callback/*")
...
)
);
return http.build();
}
}
ClientRegistration.redirectUri와 baseUri가 일치해야 한다.
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
.build();
UserInfo Endpoint는 많은 설정 옵션을 가지고 있다.
OAuth2User.getAuthorities()(or OidcUser.getAuthorities())가 승인된 인증 목록을 포함하고 있다.
OAuth2UserRequest.getAccessToken().getScopes()와 SCOPE_ 프리픽스를 가지고 있는OAuth2AuthenticationToek.getAuthorities()는 hasRole(’USER’)나 hasRole(’ADMIN’) 같은 인가 요청을 위해 사용된다.GrantedAuthoritesMapper 구현체를 만들고 설정하는 방식GratnedAuthoritesMapper보다 어렵지만 더 유연한 방식DefaultOAuth2UserService는 OAuth2UserService의 구현체이며 OAuth 2.0 Provider 표준을 지원한다.
OAuth2UserService는 인증 흐름 중에 클라이언트에 부여된 액세스 토큰을 사용하여 UserInfo Endpoint에서 end-user(리소스 소유자)의 사용자 속성을 가져오고AuthenticatedPrincipal을OAuth2User형식으로 반환한다.
DefaultOAuth2UserService는 UserInfo Endpoint에서 사용자 속성을 요청할 때RestOperations인스턴스를 사용한다.DefaultOAuth2UserService.setRequestEntityConverter()를 통해 UserInfo 요청의 전처리를 커스터마이징할 수 있다.
- 커스텀
Converter<OAuth2UserRequest, RequestEntitiy<?>>를 통해- 기본 구현체인
OAuth2UserRequestEntityConverter는 UserInfo 요청을 표현하는RequestEntity를Authorization헤더 안의OAuth2AccessToken을 통해 만든다.DefaultOAuth2UserService.setRestOperations()와 커스텀 설정된RestOperations을 통해 UserInfo 응답 후처리를 커스터마이징할 수 있다.
- 기본
RestOperations설정 방법
```java
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
// OAuth2ErrorResponseErrorHandler는 OAuth 2.0 Error(400)을 처리하는 ResponseHandler다.
```
DefaultOAuth2UserService를 커스텀하거나 커스텀 OAuth2UserService를 제공하는 설정
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userService(this.oauth2UserService())
...
)
);
return http.build();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
...
}
}
OidcUserService는 OpenID Connect 1.0 Provider를 지원하는 OAuth2UserService 구현체다.OidcUserService는 DefaultOAuth2UserService로 UserInfo Endpoint의 사용자 속성을 요청한다.OidcUserService.setOauthUserService()로 DefaultOAuth2UserService를 커스텀 설정하여 UserInfo 요청 전처리나 응답 처리를 커스텀할 수 있다.
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(this.oidcUserService())
...
)
);
return http.build();
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
...
}
}
OidcIdTokenDecoderFactory는 OidcIdToken 서명 검증을 위해 사용하는 JwtDecoder를 제공한다.
RS256이지만 등록된 클라이언트에 따라 다를 수 있다.아래 코드는 ClientRegistration 인스턴스에 대해 기본적으로 MacAlgorithm.HS256을 설정한 코드다.
@Bean
public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
return idTokenDecoderFactory;
}