오픈 인증(OAuth)은 사용자 계정의 로그인이나 비밀번호 없이도 애플리케이션이 사진, 캘린더 또는 소셜 미디어 게시물과 같은 최종 사용자의 보호된 리소스에 액세스할 수 있는 권한을 부여하는 개방형 표준 인증 프레임워크이다.
출처 : https://www.ibm.com/kr-ko/think/topics/oauth
OAuth는 우리가 익숙하게 사용하는 소셜 로그인에 자주 사용된다.
소셜로그인 기능을 사용하면, 우리가 이용하는 웹사이트나 앱에 회원가입을 하지 않고 구글, 네이버 등의 대형 웹사이트에 로그인 하는 것으로 서비스를 이용할 수 있게 된다.
이게 가능한 이유는 소셜로그인 기능을 구현한 웹사이트/앱에서 OAuth 프로토콜을 통해 구글, 네이버 등에 저장되어 있는 사용자 정보를 요청해 받아오고, 그 정보를 가지고 회원가입, 로그인 인증을 수행하기 때문이다.
OAuth를 이용하지 않는 일반적인 회원가입/로그인 기능을 구현한다고 생각해보자.
서버는 사용자에게 회원가입을 요구할 것이고, 그 과정에서 서비스 제공에 필요한 사용자 정보(이름, 이메일, 전화번호 등등...)와 아이디, 비밀번호와 같은 로그인 정보를 받게 될 것이다.
이러한 서버는 기본적으로 사용자 입장에서 신뢰하기 어렵다.
사용하고자 하는 서비스의 서버 보안 수준이 어느정도인지 알기 어렵고, 만약 취약한 보안 약점을 가지고 있는 서버라면 사용자의 로그인 정보가 탈취될 수도 있다.
특히, 대부분의 사용자들은 같은 로그인 정보(아이디, 비밀번호)를 여러 서비스에서 돌려쓰기 때문에, 한번 유출된 로그인 정보는 치명적인 결과를 낳을 수 있다.
OAuth2를 이용한 소셜로그인은 그러한 정보들을 사용자에게 직접 받는 것이 아니라, 대형 서드파티(구글, 네이버 등)로부터 받아오게 된다.
또한, 그러한 정보를 받기 위해 수행하는 인증(로그인) 절차 역시 대형 서드파티로부터 실시하기 때문에, 사용자는 신뢰할 수 없는 서비스에 직접 로그인 할 때보다 훨씬 더 안전한 인증 과정을 거칠 수 있게 된다.
요즘 대형 서드파티에서는 로그인 상태를 유지시켜주는 기능이 대부분 있기 때문에, 사용자 입장에서는 번거롭게 로그인 정보를 매번 입력할 필요 없이 소셜 로그인 버튼 클릭 한번을 통해 편리하게 다른 서비스를 이용할 수 있는 것 역시 큰 장점이다.
개발자 입장에서도 편의성 측면에서 장점이 있다.
아이디, 비밀번호를 입력받아 로그인을 수행하는 로직을 작성할 필요가 없어지기 때문에 그만큼 작성할 코드가 줄어들고, 그 정보들을 관리하는데 들어가는 리소스 역시 줄어든다.
Resource Owner(User) : 우리가 만드는 서비스를 사용하는 유저를 말한다.
Client : 우리가 만드는 서버 어플리케이션 그 자체를 4️⃣말한다.
Resource Server : User의 정보(이름, 이메일, 프로필 사진 등...)을 저장하고 있는 서버(구글, 네이버, Github 등)를 의미한다.
Authenticattion Server : Resource Server에 저장된 정보에 접근할 때, User가 제출한 로그인 정보를 토대로 인증 과정을 수행하고 AccessToken을 발급해 주는 인증 서버이다.
유저를 Resource Owner라고 부르는 이유는 Client에서 소셜 로그인 과정을 수행하기 위해 Resource Server에 요청하는 정보의 주인이 바로 유저이기 때문이다.
OAuth 인증 과정에는 몇가지 종류가 있는데, 본 포스트에서는 가장 흔하게 이용되는 Authorizatio code 방식을 설명하겠다.
User가 Client에 로그인 요청을 한다.
Client는 Authorization Server의 로그인 페이지로 User를 리다이렉트 시킨다.
User가 Authorization Server가 제공한 로그인 페이지에서 로그인 정보를 입력한다.
Authorization Server에서 로그인이 성공하면, User를 Client에서 미리 지정해 둔 URI로 Authorization code와 함께 리다이렉션 시킨다.
Client는 전달받은 Authorization code를 가지고 Authorization Server에 Access Token을 요청한다.
Authorization Server는 Client로부터 받은 code를 검증하고, 검증에 성공하면 Access Token을 Client에 전달한다.
Client는 전달받은 Access Token을 Resource Server에 제시하고, 서비스에 필요한 정보를 제공받는다.
스프링 시큐리티에서 OAuth2 인증을 활성화 시키면 아래와 같은 FilterChain을 볼 수 있다.
여기서 주목해야 할 부분은 OAuth2AuthorizationRequestRedirectFilter
, 그리고 OAuth2LoginAuthenticationFilter
2가지다.
이 필터가 하는 역할은 3️⃣에서 작성한 인증 흐름에서 1, 2번을 담당한다.
즉, 사용자의 로그인 요청을 받았을 때, 그 요청을 각 소셜로그인 서비스 제공자(구글, 네이버, 카카오 등)의 로그인 페이지로 리다이렉트 시키는 것이다.
위 코드는 OAuth2AuthorizationRequestRedirectFilter
클래스의 doFilterInternal
메소드이다.
authorizationRequestResolver
가 resolve
메소드를 호출하여 OAuth2AuthorizationRequest
를 리턴한다.OAuth2AuthorizationRequest
객체가 null이 아니면 sendRedirectForAuthorization
메소드를 호출해 소셜 로그인 페이지로 리다이렉트 시킨다.
같은 클래스의 sendRedirectForAuthorization
클래스를 보자.
만약 OAuth 인증 타입이 Authorization code 방식일 경우, authorizationRequestRepository
에 해당 요청을 저장해 놓음으로써 인증 요청을 유지시킨다.
이후 authorizationRequest
에 저장된 RequestUri를 가지고 리다이렉트를 수행한다.
사용자는 리다이렉트된 페이지에서 로그인을 수행하고, Authorization code가 우리 클라이언트로 리다이렉트되어 넘어오게 된다.
이때 code가 넘어오는 Uri는 구글이나 네이버, 카카오에 OAuth 프로젝트를 등록하며 입력했던 Redirect_Uri이다.
OAuth 인증 흐름의 4️⃣ ~ 7️⃣부분을 담당하는 부분이다.
this.getAuthenticationManager().authenticate(authenticationRequest)
)authenticationResult
에 저장되고, 이 정보를 authorizedClientRepository에 저장하여 사용자 인증 상태를 활성화하게 된다.OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
.getAuthenticationManager()
.authenticate(authenticationRequest);
상술한 AccessToken 발급, 사용자 정보 요청 과정은 위 코드에서 수행한다.
ProviderManager는 각종 인증을 지원하는 Provider들을 모아서 관리하는 클래스인데, 실질적
인증 절차를 수행하는 것은 authenticate
메소드이다.
이 메소드 자체가 하는 역할은 그렇게 복잡하지 않다.
인자로 받은 authentication의 실제 자식 타입이 무엇인지 확인하고(브레이크 포인트 첫 번째), 이를 지원하는 Provider에게 인증 절차를 위임해준다(브레이크 포인트 두 번째).
OAuth2 인증 과정에서 ProviderManager로부터 인증 절차를 위임받는 클래스는 OAuth2LoginAuthenticationProvider
이다.
Authentication
객체를 OAuth2LoginAUthenticationToken
객체로 캐스팅한다.return null
을 해준다.authorizationCodeAuthenticationToken
을 생성한다.(브레이크 포인트 첫 번째)UserService
의 loadUser
메소드를 실행하여 받아온 유저 정보를 클라이언트 단에 저장하고, 불러온다.UserService
는 보통 개발자가 직접 만든 커스텀 UserService
가 들어가는게 보통이고, 따로 구현한게 없다면 DefaultOAuth2UserService
가 들어가게 된다.