Ordinary
About

Spring Security OAuth2

profileordilov / 2022. 2. 5
OAuth2 Login

application.yml 설정하기

spring: security: oauth2: client: registration: google: client-id: google-client-id client-secret: google-client-secret
  1. spring.security.oauth2.client.registration 까지 고정적으로 쓰이는 속성입니다.
  2. google 처럼 고정된 id에 따라 OAuth2 인증을 진행할 수 있습니다.

OAuth2 인증 시 결과

OAuth2 로그인을 마치면 기본 개인정보를 받으면서 인증 세션이 연결됩니다.

부가적인 설정

redirect uri 나 인증 방식 그리고 OAuth2 인증 범위등을 설정할 수 있습니다.

redirect-uri: {baseUrl}/login/oauth2/code/{registrationId}

redirectUri의 기본형식입니다. 그대로 입력하면 baseUrl에는 현재 주소가 들어갑니다. registrationId는 위에서 사용한 Id가 그대로 들어가게 됩니다. 따라서 redirectUri를 수정한다면 위의 템플릿을 이용해서 수정하면 됩니다.

OAuth2 설정 오버라이드하기

OAuth2 설정을 오버라이드하는 방법에는 3가지가 있습니다. 그 중에서 스프링을 사용한다면 2가지 방법을 사용하게 됩니다.

ClientRegistrationRepository @Bean

@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(); } }

빈을 등록해서 기본적으로 설정되어 있는 구성들을 변경할 수 있습니다.

WebSecurityConfigurerAdapter

@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) .oauth2Login(withDefaults()); } }

좀 더 범용적으로 쓰이는 WebSecurityConfigurerAdapter를 사용할 수 있습니다. oauth2Login()에 변경할 내용들을 추가해주면 됩니다. 두 개를 같이 사용하는 방법도 있습니다. 먼저 oauth2Login()에서 변경할 수 있는 구성 요소들입니다.

@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(authorization -> authorization ... ) .redirectionEndpoint(redirection -> redirection ... ) .tokenEndpoint(token -> token ... ) .userInfoEndpoint(userInfo -> userInfo ... ) ); } }
  • Authorization Endpoint: 클라이언트가 인증을 위해 접속하는 곳입니다.
  • Token Endpoint: 클라이언트가 접근 토큰으로 인가를 받는 곳입니다.
  • Redirection Endpoint: 인증 서버에서 인증 정보를 담은 결과를 보낼 곳입니다.
  • UserInfo Endpoint: 인증정보로 user에 대한 정보를 처리하는 곳입니다.

Login 페이지 처리

Spring Security를 사용하면 기본적으로 로그인 페이지로 id, password 폼 창이 나옵니다. OAuth2-Client를 설정하고 yml에 등록된 OAuth2 id가 있다면 소셜 로그인 페이지로 바뀝니다. 이런 Login Page처리는 DefaultLoginPageGeneratingFilter에서 이뤄집니다. 각 이름은 등록된 ClientRegistration.clientName으로 표시됩니다. 각 링크는 OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{registrationId}"이 됩니다. 예를 들면 /oauth2/authorization/google 방식으로 구성됩니다. 이런 로그인 페이지를 수정하려면 위치를 수정하려면 oauth2Login()에서 수정합니다.

@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2 -> oauth2 .loginPage("/login/oauth2") ... .authorizationEndpoint(authorization -> authorization .baseUri("/login/oauth2/authorization") ... ) ); } }

Redirection Endpoint

기본적으로 OAuth2 응답 uri는 /login/oauth2/code/*로 설정되어 있습니다. 이 정보는 OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI에 정의되어 있습니다.

@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2 -> oauth2 .redirectionEndpoint(redirection -> redirection .baseUri("/login/oauth2/callback/*") ... ) ); } }

원하는 redirectUri로 수정하려면 위처럼 수정하면 가능합니다. 당연히 각 OAuth2 페이지에서 설정한 redirectUri 와 일치해야 합니다.

UserInfo Endpoint

Mapping User Authorities

OAuth2 에서 받은 인증 정보로 사용자마다 권한을 부여할 수 있습니다. 기본적으로 OAuth2 에서 제공되는 Authority는 OAuth2User.getAuthorities()에서 제공됩니다. 이 정보를 가지고 GrantedAuthority에 권한들을 매치시켜야 합니다. 그리고 이런 권한은 OAuth2AuthenticationToken에 제공됩니다.

Using a GrantedAuthoritiesMapper

GrantedAuthorityMapper 구현체를 만들어 매핑시킬 수 있습니다.

@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2 -> oauth2 .userInfoEndpoint(userInfo -> userInfo .userAuthoritiesMapper(this.userAuthoritiesMapper()) ... ) ); } private GrantedAuthoritiesMapper userAuthoritiesMapper() { return (authorities) -> { Set<GrantedAuthority> mappedAuthorities = new HashSet<>(); authorities.forEach(authority -> { if (OidcUserAuthority.class.isInstance(authority)) { OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority; OidcIdToken idToken = oidcUserAuthority.getIdToken(); OidcUserInfo userInfo = oidcUserAuthority.getUserInfo(); // Map the claims found in idToken and/or userInfo // to one or more GrantedAuthority's and add it to mappedAuthorities } else if (OAuth2UserAuthority.class.isInstance(authority)) { OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority; Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes(); // Map the attributes found in userAttributes // to one or more GrantedAuthority's and add it to mappedAuthorities } }); return mappedAuthorities; }; } }

Delegation-based strategy with OAuth2UserService

GrantedAuthoritiesMapper 대신 OAuth2UserService를 구현할 수 있습니다. 입력값으로는 OAuth2UserRequest에서 OAuth2AccessToken을 받아올 수 있습니다. 이 과정에서 사용자에게 인증 정보를 처리하기 전에 접근 토큰으로 인증 정보를 불러올 수 있습니다.

@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2 -> oauth2 .userInfoEndpoint(userInfo -> userInfo .userService(this.oauth2UserService()) ... ) ); } private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() { ... } }

DefaultOAuth2UserService는 OAuth2UserService의 구현체입니다. 기본적으로 RestTemplate을 이용해 토큰에서 인증 정보를 불러오는 역할을 담당합니다. 이렇게 받아온 인증 정보로 AuthenticatedPrincipal을 반환합니다. 인증 처리 과정을 바꾸는 방법은 두 가지입니다.

  • 직접 OAuth2UserService 구현하기
  • DefaultOAuth2UserService에서 제공하는 set함수에 파라미터 제공하기