[Spring Security] 스프링 시큐리티 OAuth2 동작 과정

정민규·2024년 12월 27일
0

1️⃣ OAuth2 개요

오픈 인증(OAuth)은 사용자 계정의 로그인이나 비밀번호 없이도 애플리케이션이 사진, 캘린더 또는 소셜 미디어 게시물과 같은 최종 사용자의 보호된 리소스에 액세스할 수 있는 권한을 부여하는 개방형 표준 인증 프레임워크이다.
출처 : https://www.ibm.com/kr-ko/think/topics/oauth

OAuth는 우리가 익숙하게 사용하는 소셜 로그인에 자주 사용된다.

소셜로그인 기능을 사용하면, 우리가 이용하는 웹사이트나 앱에 회원가입을 하지 않고 구글, 네이버 등의 대형 웹사이트에 로그인 하는 것으로 서비스를 이용할 수 있게 된다.

이게 가능한 이유는 소셜로그인 기능을 구현한 웹사이트/앱에서 OAuth 프로토콜을 통해 구글, 네이버 등에 저장되어 있는 사용자 정보를 요청해 받아오고, 그 정보를 가지고 회원가입, 로그인 인증을 수행하기 때문이다.

2️⃣ OAuth 인증의 장점

OAuth를 이용하지 않는 일반적인 회원가입/로그인 기능을 구현한다고 생각해보자.

서버는 사용자에게 회원가입을 요구할 것이고, 그 과정에서 서비스 제공에 필요한 사용자 정보(이름, 이메일, 전화번호 등등...)와 아이디, 비밀번호와 같은 로그인 정보를 받게 될 것이다.

이러한 서버는 기본적으로 사용자 입장에서 신뢰하기 어렵다.
사용하고자 하는 서비스의 서버 보안 수준이 어느정도인지 알기 어렵고, 만약 취약한 보안 약점을 가지고 있는 서버라면 사용자의 로그인 정보가 탈취될 수도 있다.

특히, 대부분의 사용자들은 같은 로그인 정보(아이디, 비밀번호)를 여러 서비스에서 돌려쓰기 때문에, 한번 유출된 로그인 정보는 치명적인 결과를 낳을 수 있다.

OAuth2를 이용한 소셜로그인은 그러한 정보들을 사용자에게 직접 받는 것이 아니라, 대형 서드파티(구글, 네이버 등)로부터 받아오게 된다.

또한, 그러한 정보를 받기 위해 수행하는 인증(로그인) 절차 역시 대형 서드파티로부터 실시하기 때문에, 사용자는 신뢰할 수 없는 서비스에 직접 로그인 할 때보다 훨씬 더 안전한 인증 과정을 거칠 수 있게 된다.

요즘 대형 서드파티에서는 로그인 상태를 유지시켜주는 기능이 대부분 있기 때문에, 사용자 입장에서는 번거롭게 로그인 정보를 매번 입력할 필요 없이 소셜 로그인 버튼 클릭 한번을 통해 편리하게 다른 서비스를 이용할 수 있는 것 역시 큰 장점이다.

개발자 입장에서도 편의성 측면에서 장점이 있다.
아이디, 비밀번호를 입력받아 로그인을 수행하는 로직을 작성할 필요가 없어지기 때문에 그만큼 작성할 코드가 줄어들고, 그 정보들을 관리하는데 들어가는 리소스 역시 줄어든다.

2️⃣ 용어 정리

Resource Owner(User) : 우리가 만드는 서비스를 사용하는 유저를 말한다.
Client : 우리가 만드는 서버 어플리케이션 그 자체를 4️⃣말한다.
Resource Server : User의 정보(이름, 이메일, 프로필 사진 등...)을 저장하고 있는 서버(구글, 네이버, Github 등)를 의미한다.
Authenticattion Server : Resource Server에 저장된 정보에 접근할 때, User가 제출한 로그인 정보를 토대로 인증 과정을 수행하고 AccessToken을 발급해 주는 인증 서버이다.

유저를 Resource Owner라고 부르는 이유는 Client에서 소셜 로그인 과정을 수행하기 위해 Resource Server에 요청하는 정보의 주인이 바로 유저이기 때문이다.

3️⃣ OAuth 인증 흐름

출처 : https://blog.naver.com/mds_datasecurity/222182943542

OAuth 인증 과정에는 몇가지 종류가 있는데, 본 포스트에서는 가장 흔하게 이용되는 Authorizatio code 방식을 설명하겠다.

  1. User가 Client에 로그인 요청을 한다.

  2. Client는 Authorization Server의 로그인 페이지로 User를 리다이렉트 시킨다.

  3. User가 Authorization Server가 제공한 로그인 페이지에서 로그인 정보를 입력한다.

  4. Authorization Server에서 로그인이 성공하면, User를 Client에서 미리 지정해 둔 URI로 Authorization code와 함께 리다이렉션 시킨다.

  5. Client는 전달받은 Authorization code를 가지고 Authorization Server에 Access Token을 요청한다.

  6. Authorization Server는 Client로부터 받은 code를 검증하고, 검증에 성공하면 Access Token을 Client에 전달한다.

  7. Client는 전달받은 Access Token을 Resource Server에 제시하고, 서비스에 필요한 정보를 제공받는다.

4️⃣ Spring Security에서의 OAuth2 인증 흐름

스프링 시큐리티에서 OAuth2 인증을 활성화 시키면 아래와 같은 FilterChain을 볼 수 있다.

여기서 주목해야 할 부분은 OAuth2AuthorizationRequestRedirectFilter, 그리고 OAuth2LoginAuthenticationFilter 2가지다.

OAuth2AuthorizationRequestRedirectFilter

이 필터가 하는 역할은 3️⃣에서 작성한 인증 흐름에서 1, 2번을 담당한다.
즉, 사용자의 로그인 요청을 받았을 때, 그 요청을 각 소셜로그인 서비스 제공자(구글, 네이버, 카카오 등)의 로그인 페이지로 리다이렉트 시키는 것이다.


위 코드는 OAuth2AuthorizationRequestRedirectFilter 클래스의 doFilterInternal 메소드이다.

  • Try문 안쪽을 보면 authorizationRequestResolverresolve 메소드를 호출하여 OAuth2AuthorizationRequest를 리턴한다.
  • 리턴 받은 OAuth2AuthorizationRequest 객체가 null이 아니면 sendRedirectForAuthorization 메소드를 호출해 소셜 로그인 페이지로 리다이렉트 시킨다.


같은 클래스의 sendRedirectForAuthorization 클래스를 보자.

만약 OAuth 인증 타입이 Authorization code 방식일 경우, authorizationRequestRepository해당 요청을 저장해 놓음으로써 인증 요청을 유지시킨다.

이후 authorizationRequest에 저장된 RequestUri를 가지고 리다이렉트를 수행한다.

사용자는 리다이렉트된 페이지에서 로그인을 수행하고, Authorization code가 우리 클라이언트로 리다이렉트되어 넘어오게 된다.

이때 code가 넘어오는 Uri는 구글이나 네이버, 카카오에 OAuth 프로젝트를 등록하며 입력했던 Redirect_Uri이다.

OAuth2LoginAuthenticationFilter

OAuth 인증 흐름의 4️⃣ ~ 7️⃣부분을 담당하는 부분이다.

  • HttpServletResponse를 파싱하여 Map 형태로 파라미터들을 추출한다.
    이때, 우리가 4️⃣에서 언급한 Authorization code가 여기에 담긴다.
  • 인증 요청 유지를 목적으로 저장해 두었던 authorizationRequest를 Repository로부터 꺼낸다.
  • 이후 AccessToken 발급을 위한 로직들이 이어진다.
    파싱한 파라미터들을 토대로 authorizationResponse를 만들고, 여기에 몇몇 정보를 더해 authenticationRequest를 만든다.
  • 발급받은 code를 통해 AccessToken을 받고, 이를 토대로 사용자 정보를 받아오는 일련의 과정은 마지막 브레이크포인트를 걸어 놓은 부분에서 수행된다. (this.getAuthenticationManager().authenticate(authenticationRequest))
  • 최종적으로 User의 Principal(이름, 이메일을 비롯한 각종 정보), AccessToken등의 정보가 authenticationResult에 저장되고, 이 정보를 authorizedClientRepository에 저장하여 사용자 인증 상태를 활성화하게 된다.

ProviderManager

OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
			.getAuthenticationManager()
			.authenticate(authenticationRequest);

상술한 AccessToken 발급, 사용자 정보 요청 과정은 위 코드에서 수행한다.

ProviderManager는 각종 인증을 지원하는 Provider들을 모아서 관리하는 클래스인데, 실질적
인증 절차를 수행하는 것은 authenticate 메소드이다.
이 메소드 자체가 하는 역할은 그렇게 복잡하지 않다.

인자로 받은 authentication의 실제 자식 타입이 무엇인지 확인하고(브레이크 포인트 첫 번째), 이를 지원하는 Provider에게 인증 절차를 위임해준다(브레이크 포인트 두 번째).

OAuth2LoginAuthenticationProvider

OAuth2 인증 과정에서 ProviderManager로부터 인증 절차를 위임받는 클래스는 OAuth2LoginAuthenticationProvider이다.

  • 우선 인자로 받은 Authentication 객체를 OAuth2LoginAUthenticationToken 객체로 캐스팅한다.
  • Oidc 인증 요청인 경우, 별도의 Provider가 처리하도록 return null을 해준다.
  • 이후 try문 안에서 authorizationCodeAuthenticationToken을 생성한다.(브레이크 포인트 첫 번째)
    이 과정에서 AccessToken 발급 및 User 정보 요청을 모두 수행하게 된다.
  • 그리고 UserServiceloadUser 메소드를 실행하여 받아온 유저 정보를 클라이언트 단에 저장하고, 불러온다.
    UserService는 보통 개발자가 직접 만든 커스텀 UserService가 들어가는게 보통이고, 따로 구현한게 없다면 DefaultOAuth2UserService가 들어가게 된다.
  • 마지막으로 authenticationResult를 만들어 리턴해준다.
profile
조금이더라도 꾸준하게.

0개의 댓글

관련 채용 정보