카테고리 없음

OAuth2.0과 레벨로그의 Github 로그인

수연초이 2022. 10. 30. 03:51
Interceptor과 Resolver에 관련된 설명은 생략합니다

OAuth?

OAuth(Open Authorization)는 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준(문서로 공개된 기술 표준)이다.

아래의 사진을 예시로 들어보자. 제 3자 서비스(인프런)은 OAuth를 바탕으로 외부 서비스(카카오, 구글, 깃허브, 페이스북, 애플)로부터 특정 자원을 공유받을 수 있다.

왜 사용할까?

OAuth가 없던 시절엔, 클라이언트는 리소스 소유자의 인증 정보를 사용하여 서버에게 보호된 리소스를 요청했다. 그렇기 때문에 제 3의 애플리케이션이 보호된 리소스를 접근하려면, 리소스 소유자가 인증 정보(예를 들면 서버의 아이디와 비밀번호)를 공유해야한다. 이 경우 사용자, 서버, 제 3의 서비스 모두에게 문제가 우려된다.

  • 제 3의 애플리케이션에서는 추후 사용을 고려해 사용자의 인증 정보를 저장해야한다. 이 정보가 유실된다면?
  • 제 3의 애플리케이션에서는 보호된 리소스에 대해 광범위하게 접근할 수 있다. 접근 가능 기간이나 범위를 지정할 수 없다.
  • 등등..

OAuth는 이러한 걱정거리를 해결해준다. 제 3의 애플리케이션은 유저의 인증 정보를 갖지 않는 대신, access token을 가져온다. 이 토큰을 사용하여 보호된 리소스에 접근할 수 있다. 참고로 OAuth는 HTTP 통신에서 사용하도록 설계되었다. 다른 프로토콜에서는 사용할 수 없다.

 

OAuth 1.0 vs 2.0

OAuth 2.0은 1.0을 완전히 다시 쓴 것으로, 목표와 사용자 경험만 공유할 뿐 이 둘은 완전히 다른 프로토콜이다. OAuth 1.0은 크게 Flickr의 인증 API와 Google의 AuthSub 이 두 가지의 독점 프로토콜을 기반으로 한다. 당시에는 최고의 솔루션이였지만, 기능 제한이나 구현에서 너무 어려운 부분이 많았기 때문에 개선이 필요하게 되었다.

구체적인 차이를 알고 싶다면 읽어보자.

 

Github 로그인 방식

흐름 이해하기

공식 홈페이지에 매우 잘 나와있다. 하지만 영어니까 시간이 된다면 한글로 작성해보자.

1. OAuth App 등록참고로 이 부분은 결이 했다.

  깃허브 Settings → Developer settings → OAuth Apps → new OAuth App에서 OAuth App을 등록한다. 등록하면 Client ID, Client secrets를 얻을 수 있다.

2. 주소창에 위에서 부여 받은 데이터를 사용해서, GET https://github.com/login/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri} 입력한다. 참고로 code의 만료 시간은 10분이다.

3. client_id, client_secret, Authorization code를 사용해서 POST https://github.com/login/oauth/access_token 요청 을 보낸다. Postman으로 테스트하면 accessToken을 얻을 수 있다. 

4. access_token을 사용해서, GET https://api.github.com/user에 요청을 보내면 해당 계정과 관련된 정보를 제공받을 수 있다.

레벨로그의 Github OAuth

흐름 이해하기

  1. 프론트에서 로그인 버튼을 누르면 client_id, redirect_uri 정보를 query param에 담아 Github 로그인 페이지로 요청을 보낸다.
  2. Github에서는 callback url에 authorization을 의미하는 code를 담아 보낸다. (10분 만료)
  3. 프론트에서는 Github가 리다이렉트 시킨 callback url에 대한 임시 방편 페이지에서 code를 추출한다.
  4. 추출한 code를 가지고 백엔드에게 회원가입 또는 로그인을 의미하는 요청을 보낸다.
  5. 백엔드는 해당 code와 client_id, redirect_uri 정보를 query param에 담아 Github에게 accessToken을 요청한다.
  6. Github에서는 code 를 access_token 으로 변경하여 보낸다.
  7. 백엔드는 해당 access_token 을 가지고 Github에 유저 정보를 요청한다.
  8. 백엔드는 응답받은 유저 정보로 이미 존재하는 회원인지 확인한다. (githubId)
  9. 존재하지 않다면 저장(회원 가입)한다.
  10. 해당 정보를 가지고 accessToken을 만들어 프론트에게 응답한다.

구현 방식

핵심 코드를 한번 살펴보자. 별거없다. 저 위의 과정을 코드에서 동작하도록만 만들면 된다. 문제는 테스트다.

OAuthService.class

public class OAuthService {

  private final MemberService memberService;
  private final OAuthClient oAuthClient;
  private final JwtTokenProvider jwtTokenProvider;

  public LoginResponse login(final GithubCodeRequest codeRequest) {
      final String code = codeRequest.getAuthorizationCode();
      final String githubAccessToken = oAuthClient.getAccessToken(code); // Github accessToken을 얻는다

      final GithubProfileResponse githubProfile = oAuthClient.getProfile(githubAccessToken); // Github 유저 리소스를 얻는다
      final Long memberId = getMemberIdByGithubProfile(githubProfile); // 유저가 존재하는지 확인. 존재하지 않는 경우 회원으로 등록

      final String token = jwtTokenProvider.createToken(memberId.toString()); 

      return new LoginResponse(token, githubProfile.getProfileUrl());
  }

GithubOAuthClient.class

public class GithubOAuthClient implements OAuthClient {

    private static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
    private static final String USER_ACCESS_URL = "https://api.github.com/user";

    private final String clientId;
    private final String clientSecret;

    public GithubOAuthClient(final String clientId, final String clientSecret) {
        this.clientId = clientId;
        this.clientSecret = clientSecret;
    }

    @Override
    public String getAccessToken(final String code) {
        final GithubAccessTokenRequest githubAccessTokenRequest = new GithubAccessTokenRequest(clientId, clientSecret,
                code);
        final HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);

        final HttpEntity<GithubAccessTokenRequest> httpEntity =
                new HttpEntity<>(githubAccessTokenRequest, headers);

        final GithubAccessTokenResponse response = new RestTemplate()
                .exchange(TOKEN_URL, HttpMethod.POST, httpEntity, GithubAccessTokenResponse.class)
                .getBody();

        if (Objects.isNull(response)) {
            throw new IllegalStateException("Github 요청에 실패했습니다.");
        }

        return response.getAccessToken();
    }

 

  • RestTemplate?
    • 스프링 어플리케이션에서 HTTP 요청할 때 사용하는 방법

Ref.