Search
📒

8-4. OAuth2와 JWT

많은 서비스에서 활용하고 있는 소셜 로그인 가능입니다. 소셜 로그인을 제공하는 서비스에 접속을 하게되면, 해당 서비스에서 사용한 자신의 계정정보를, 해당 서비스 제공자 외의 서비스에서 사용할 수 있는 것이 소셜 로그인 입니다.
다만 소셜 로그인은, 실제로 제공자 서비스에 로그인을 하는 과정이 들어가지만, 동일한 계정을 활용해서 다른 서비스에 들어가는 것이 아니라, 해당 계정의 정보만을 제공하기 위한 동의의 과정이라고 생각하여야 합니다.

OAuth2란?

OAuth2는 Open Authorization 2 의 약자로, 일반적으로 저희가 소셜 로그인이라고 알고 있는 기능의 구현 이론입니다.
기본적인 OAuth2의 흐름은 다음과 같습니다.
1.
사용자가 로그인이 필요한 서비스 요청
2.
사용자에게 로그인 정보 전달 요청
3.
사용자가 전달한 로그인 정보를 제공자(소셜 서비스)의 인증 서버로 전달
4.
정상적인 인증 정보일 경우 access token 발급
5.
access token을 사용하여 제공자의 자원 서버로 전달
6.
접속 요청 사용자의 정보 전달
7.
사용자의 정보를 기반으로 서비스 제공

현실에서의 모습

현실에서는 네이버 아이디로 로그인할 경우 네이버 화면으로 이동하게 되고, 카카오 아이디로 로그인할 경우 카카오 화면으로 이동하게 됩니다. 그래서 개별 서비스에서 로그인을 시도하게 되면, 서비스 제공자 화면으로 이동 후, 해당 화면에서 로그인을 진행했다는 사실을 소셜 로그인을 사용하는 서비스에 알려줘야 하는 과정이 포함되어야 합니다.
1.
사용자가 로그인이 필요한 서비스 요청
2.
사용자가 소셜 로그인 제공자 선택
3.
사용자가 선택한 소셜 로그인 화면으로 redirect
4.
제공자(소셜 서비스) 인증 화면에 사용자가 인증정보 전달
5.
정상적인 인증 정보일 경우 access token 발급하여 미리 설정된 URL로 전달
6.
access token을 사용하여 제공자의 자원 서버로 전달
7.
접속 요청 사용자의 정보 전달
8.
사용자의 정보를 기반으로 서비스 제공
소셜 로그인을 진행하고 난 뒤, 제공받는 access token을 이용하여 서비스 제공자의 서버에 정보 제공을 요청할 수 있습니다.

네이버 로그인 구현하기

네이버 아이디로 로그인 Application 생성

OAuth2 Client 설정

Application을 생성하였다면 이제 Spring Boot에서 소셜 로그인 기능을 구현해 봅시다.
spring: ... security: oauth2: client: registration: naver: # OAuth2 제공자에서 사용 Application을 구분하기 위한 ID와 Key 입니다. client-id: Kj_n_GJrDbkhnYIVj1W6 client-secret: # Application에서 작성한 redirect URI 입니다. redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId} # OAuth에 정의된 정보 제공 방식에 대한 설정입니다. authorization-grant-type: authorization_code # 어떤 정보를 제공받을지에 대한 설정입니다. Naver에 등록시 설정했던 내용을 # 바탕으로 설정해야 합니다. scope: email client-name: Naver provider: naver: # 사용자 로그인을 위한 URI 입니다. authorization-uri: https://nid.naver.com/oauth2.0/authorize # Access Token 발급, 갱신, 삭제 등을 위한 URI 입니다. token-uri: https://nid.naver.com/oauth2.0/token # Access Token을 가지고 사용자의 정보를 요청하기 위한 URI 입니다. user-info-uri: https://openapi.naver.com/v1/nid/me # 사용자의 정보를 받아온 뒤, 일반적으로 JSON 형태의 데이터 중 # 어떤 Key가 사용자 아이디 인지를 알려주기 위한 설정입니다. user-name-attribute: response
YAML
복사
Spring Security에서 어느정도의 추상화가 진행되어 있기 때문에, 적당한 설정을 가지고 API 연동부를 만들어 줄 수 있습니다. 기본적으로,
client: OAuth2 Client, 즉 소셜 로그인 기능을 사용하고자 하는 어플리케이션이 필요로 하는 설정 내역입니다. 기본적으로 providerregistration 을 등록을 해주게 됩니다.
provider: OAuth2 제공자에 대한 정보를 제공하기 위한 설정입니다.
registration: OAuth2 Client로서 필요한 정보를 작성하기 위한 설정입니다.
정도로 이해하시면 되겠습니다. 각각 Application 내부에 Map 의 형태로 저장이 되는데, naver 가 Key, 그 내용물이 클래스의 형태로 저장된다고 보시면 되겠습니다.
@ConfigurationProperties(prefix = "spring.security.oauth2.client") public class OAuth2ClientProperties implements InitializingBean { // Provider와 Registration은 내부에 정의된 정적 클래스 입니다. /** * OAuth provider details. */ private final Map<String, Provider> provider = new HashMap<>(); /** * OAuth client registrations. */ private final Map<String, Registration> registration = new HashMap<>(); ...
Java
복사
OAuth2ClientProperties
참고

OAuth2UserService 구현

기본적으로 앞서 설명한 OAuth2 과정에서, 외부 제공자에게 받아온 사용자 정보를 제공하는 인터페이스는 이미 정의되어 있습니다. 해당 인터페이스를 정의하고, WebSecurityConfig 에서 사용하도록 설정하는 과정이 필요합니다.
@FunctionalInterface public interface OAuth2UserService<R extends OAuth2UserRequest, U extends OAuth2User> { U loadUser(R userRequest) throws OAuth2AuthenticationException; }
Java
복사
하나의 함수가 있는데, loadUser 함수입니다. 로그인 요청을 받아서, 로그인 정보를 반환하는 간단한 함수입니다. 네이버를 위해 구현한 OAuth2UserService를 확인해 봅시다.
@Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(userRequest); String registrationId = userRequest.getClientRegistration().getRegistrationId(); String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); Map<?, ?> refinedAttributes = (Map<?, ?>) oAuth2User.getAttributes().get("response"); UserEntity user = new UserEntity(); user.setUsername((String) refinedAttributes.get("email")); return new DefaultOAuth2User( Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")), (Map<String, Object>) refinedAttributes, "email"); }
Java
복사
NaverOAuth2Service.java
DefaultOAuth2UserService() 를 사용하면, 기본적인 OAuth2의 플로우를 기준으로 작동하는 제공자들에 대하여, application.yml 에 작성한 내용을 기준으로 작동하도록 설정되어 있는 상태입니다. 이 DefaultOAuth2UserService 클래스 내부에서 설정을 기준으로 OAuth2 제공자와 요청 응답을 주고받아 사용자의 정보를 반환해 줍니다. 전달받은 OAuth2User 객체를 기준으로, 특정 데이터를 Spring Boot Application에서 사용할 데이터로 전환하여, loadUser 함수의 반환값으로 활용합니다.

JWT

마지막으로 JWT를 살펴봅시다. OAuth와 연관성이 있기도 하고, 없기도 합니다. 기본적으로 OAuth2 과정에서 필요한 Access Token을 만들기 위한 제안된 표준입니다. 사용자의 데이터를 주고받기 위한 Token의 일종입니다. 아래 내용을 확인해보면,
두개의 . 이 포함된 문자열이 JWT 입니다. 각각 . 을 기준으로 header.payload.signature 로 구성되어 있으며, 아래의 내용을 암호화한 것입니다.
// header { "alg": "HS256", "typ": "JWT" } // payload { "sub": "12394728", "name": "aquashdw", "iat": 1516239022 } // signature HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), "secret-key" )
JSON
복사
JWT는 OAuth2의 Access Token, 또는 단순 SSO 서버에서 사용자 정보를 제공하는 용도로도 활용됩니다. HTTPS의 공개키 암호화 방식을 채택하였기 때문에, 해석은 누구나 할 수 있지만, 생성은 제공자 서버에서만 할 수 있어서 변조가 불가능하다는 점을 들어 많이 사용됩니다.
1.
제공자 서버에서 비밀키를 이용해 암호화를 진행합니다.
2.
JWT 내용은 공개키를 이용해서 복호화할 수 있기에, 어디에서든 확인이 가능합니다.
3.
JWT를 만들기 위해선 비밀키가 필요하기 때문에, JWT를 변조하여 없는 권한을 만드는 등의 행위는 불가능합니다