Spring Security ‐ OAuth 2.0 oauth2Login - thought-corner/backend-roadmap GitHub Wiki

OAuth2LoginConfigurer 초기화 이해

  • AuthorizationEndpointConfig : 브라우저를 인가서버로 보낸다.(프론트 채널)
  • RedirectionEndpointConfig : 코드를 들고 돌아오는 지점
  • TokenEndpointConfig : 코드를 토큰으로 바꾼다.(백 채널)
  • UserInfoEndpointConfig : 토큰으로 사용자를 가져와 권한화한다.

OAuth 2.0 Login Page 생성 방법

public static final String DEFAULT_LOGIN_PAGE_URL = "/login";
public static final String ERROR_PARAMETER_NAME = "error";

public DefaultLoginPageGeneratingFilter(UsernamePasswordAuthenticationFilter authFilter) {
    this.loginPageUrl = DEFAULT_LOGIN_PAGE_URL;
    this.logoutSuccessUrl = DEFAULT_LOGIN_PAGE_URL + "?logout";
    this.failureUrl = DEFAULT_LOGIN_PAGE_URL + "?" + ERROR_PARAMETER_NAME;
    if (authFilter != null) {
        initAuthFilter(authFilter);
    }
}
  • 기본적으로 OAuth 2.0 로그인 페이지는 DefaultLoginPageGeneratingFilter가 자동으로 생성해준다.
  • 디폴트 로그인 페이지를 재정의하는 것도 가능하다.

Authorization Code 요청

  • 클라이언트는 사용자 브라우저를 통해 인가 서버의 권한 부여 엔드포인트로 리다이렉션하여 권한 코드 부여 흐름을 시작하게 된다.
  • DefaultOAuth2AuthorizationRequestResolver : 웹 요청에 대해 OAuth2AuthorizationRequest 객체를 최종 완성한다. 여기서 registrationId를 추출하고 이를 사용해서 ClientRegistration을 가져와 OAuth2AuthorizationRequest를 빌드한다.
  • 토큰 엔드포인트 요청 파라미터를 담는 객체로서 인가 응답을 연계하고 검증할 때 사용된다.
  • AuthorizationRequestRepository는 인가 요청을 내보내는 시점부터 인가 응답을 받는 시점까지, 즉 리다이렉트로 브라우저가 인가서버에 다녀오는 구간 동안 OAuth2AuthorizationRequest를 보관한다.
  • 리다이렉트 직전에 saveAuthorizationRequest로 저장하고 콜백에서 removeAuthorizationRequest로 꺼내면서 곧바로 삭제하므로 1회용이다. 이렇게 남겨둔 값으로 돌아온 응답의 state를 대조하고 PKCE code_verifier를 복원하기 때문에 단순한 임시 보관소 개념이 아니라 CSRF와 인가 코드 탈취를 막는 장치의 일부로 볼 수 있다.

Access Token 교환

  • 인가서버로부터 리다이렉트되면서 전달된 code를 인가서버의 Access Token으로 교환하고 Access Token이 저장된 OAuth2LoginAuthenticationTokenAuthenticationManager에 위임해 UserInfo 정보를 요청해서 최종 사용자로 로그인한다.
  • this.authorizedClientRepository.saveAuthorizedClient()를 사용해 OAuth2AuthorizedClient를 저장한다.
  • 인증에 성공하면 OAuth2AuthenticationToken이 생성되고 SecurityContext에 저장되어 인증 처리를 완료한다.
  • 인가서버로부터 리다이렉트 된 이후의 프로세스를 처리하며 Access Token으로 교환하고 이 토큰을 사용해 UserInfo 처리를 담당한다.
  • scope에 openid가 포함되어 있으면 OidcAuthorizationCodeAuthenticationProvider를 호출하고 아니면 OAuth2AuthorizationCodeAuthenticationProvider를 호출한다.
  • 권한 코드 부여 흐름을 처리하는 AuthenticationProvider이다.
  • 인가서버에 Authorization Code와 Access Token의 교환을 담당한다.
  • OpenID Connect Core 1.0 권한 코드 부여 흐름을 처리하는 AuthenticationProvider이며 요청 scope에 openid가 존재할 경우 실행된다.
  • 인가서버의 token 엔드포인트로 통신을 담당하며 Access Token을 받은 후 OAuth2AccessTokenResponse에 저장하고 반환한다.

1. OIDC 케이스에서 Access Token 요청 흐름 정리

2. OAuth2 케이스에서 Access Token 요청 흐름 정리

OAuth 2.0 User 모델 1 - OAuth2UserService

  • 액세스 토큰을 사용해서 UserInfo 엔드포인트 요청으로 최종 사용자의 속성을 가져오며 OAuth2User 타입의 객체를 리턴한다.
  • 구현체로는 DefaultOAuth2UserServiceOidcUserService가 제공된다.
  • 표준 OAuth 2.0 Provider를 지원하는 OAuth2UserService 구현체다.
  • OAuth2UserRequest에 액세스 토큰을 담아 인가서버와 통신 후 사용자의 속성을 가지고 온다.
  • 최종 OAuth2User 타입의 객체를 반환한다.
  • OpenID Connect 1.0 Provider를 지원하는 OAuth2UserService 구현체다.
  • OidcUserRequest에 있는 ID Token을 통해 인증 처리를 하며 필요시 DefaultOAuth2UserService를 사용해서 UserInfo 엔드포인트의 사용자 속성을 요청한다.
  • 최종 OidcUser 타입의 객체를 반환한다.

1. 일반 OAuth2

  • scope에 openid가 없다.

2. OIDC - UserInfo 생략

  • 인가서버와 추가 통신 없이 id_token만으로 끝낸다.

3. OIDC - UserInfo 호출

  • 인가서버와 추가 통신을 거친다.

OAuth 2.0 User 모델 2 - OAuth2User와 OidcUser

  • 시큐리티는 UserAttributes 및 ID Token Claims을 집계 & 구성하여 OAuth2UserOidcUser 타입의 클래스를 제공한다.

1. OAuth2User

  • OAuth 2.0 Provider에 연결된 사용자 주체를 나타낸다.
  • 최종 사용자의 인증에 대한 정보인 Attribute를 포함하고 있으며, first_name, middle_name, last_name, email, phone_number, address 등으로 구성된다.
  • 기본 구현체는 DefaultOAuth2User이며 인증 이후 Authentication의 principal 속성에 저장된다.

2. OidcUser

  • OAuth2User를 상속한 인터페이스로 OIDC Provider에 연결된 사용자 주체를 나타낸다.
  • 최종 사용자의 인증에 대한 정보인 Claims를 포함하고 있으며, OidcIdTokenOidcUserInfo에서 집계 및 구성된다.
  • 기본 구현체는 DefaultOidcUser이며, DefaultOAuth2User를 상속하고 있으며 인증 이후 Authentication의 principal 속성에 저장된다.
  • OAuth 2.0 로그인을 통해 인증받은 최종 사용자의 Principal에는 OAuth2User 혹은 OidcUser 타입의 객체가 저장된다.
  • 권한 부여 요청 시 scope 파라미터에 openid를 포함했다면 OidcUser 타입의 객체가 생성되며 OidcUserOidcUserInfoOidcIdToken을 가지고 있으며 최종 사용자에 대한 Claims 정보를 포함하고 있다.
  • OAuth2UserAuthority는 인가서버로부터 수신한 scope 정보를 집계해서 권한 정보를 구성한다.
  • OidcUser 객체를 생성할 때 ID 토큰이 필요한데 이 때, JWT로 서명된 Id Token은 JWS(Json Web Signature)로 서명되어 있기 때문에 반드시 정해진 알고리즘에 의한 검증이 성공하면 OidcUser 객체를 생성해야 한다.

OAuth 2.0 Provider UserInfo 엔드포인트

  • loadUser() : 액세스 토큰을 사용자 객체로 교환하는 것이다.
  • OAuth2UserRequestRequestEntity로 컨버터한다.
  • RequestEntity로 인가서버에 요청하고 ResponseEntity로 응답받는다.
  • OAuth2User 타입의 객체를 반환한다.

OpenID Connect Provider UserInfo 엔드포인트

  • 내부에 DefaultOAuth2UserService를 가지고 있으며 OIDC 사양에 부합할 경우 OidcUserRequest를 넘겨 주어 인가서버와 통신한다.
  • OidcUser 타입의 객체를 반환한다.

OAuth 2.0 OpenID Connect 로그아웃

  • 클라이언트는 로그아웃 엔드포인트를 사용해 웹 브라우저에 대한 세션과 쿠키를 지운다.
  • 클라이언트 로그아웃 성공 후 OidcClientInitiatedLogoutSuccessHandler를 호출해 OpenID Provider 세션 로그아웃을 요청한다.
  • OpenID Provider 로그아웃이 성공하면 지정된 위치로 리다이렉트된다.
  • 인가서버 메타데이터 사양에 있는 로그아웃 엔드포인트는 end_session_endpoint로 정의되어 있다.

Spring MVC 인증 객체 참조

  • oauth2Login()으로 인증을 받게 되면 AuthenticationOAuth2LoginAuthenticationToken 타입 객체로 바인딩된다.
  • principal에는 OAuth2User 타입 혹은 OidcUser 타입의 구현체가 저장된다.

추가 : @AuthenticationPrincipal

  • Spring Security는 AuthenticationPrincipalArgumentResolver를 제공한다. 이 리졸버는 현재 Authentication.getPrincipal()을 Spring MVC 메서드 인자로 자동 주입해준다.
  • @EnableWebSecurity를 사용하면 이 리졸버가 Spring MVC 설정에 자동으로 추가된다.
  • AuthenticationPrincipalArgumentResolver를 제대로 설정하고 나면 Spring MVC 계층을 Spring Security로부터 완전히 분리할 수 있다.