스프링부트 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;
}