OAuth2.0과 레벨로그의 Github 로그인
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
흐름 이해하기
- 프론트에서 로그인 버튼을 누르면 client_id, redirect_uri 정보를 query param에 담아 Github 로그인 페이지로 요청을 보낸다.
- Github에서는 callback url에 authorization을 의미하는 code를 담아 보낸다. (10분 만료)
- 프론트에서는 Github가 리다이렉트 시킨 callback url에 대한 임시 방편 페이지에서 code를 추출한다.
- 추출한 code를 가지고 백엔드에게 회원가입 또는 로그인을 의미하는 요청을 보낸다.
- 백엔드는 해당 code와 client_id, redirect_uri 정보를 query param에 담아 Github에게 accessToken을 요청한다.
- Github에서는 code 를 access_token 으로 변경하여 보낸다.
- 백엔드는 해당 access_token 을 가지고 Github에 유저 정보를 요청한다.
- 백엔드는 응답받은 유저 정보로 이미 존재하는 회원인지 확인한다. (githubId)
- 존재하지 않다면 저장(회원 가입)한다.
- 해당 정보를 가지고 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.
- [Tecoble] OAuth 개념 및 동작 방식 이해하기
- 매트집 Github 로그인 이슈
- Oauth 개념정리Oauth2.0 인증과정 정리 및 깃허브 로그인 세팅하기(그린론 블로그)
- OAuth 2.0 인증 과정
- [WEB] 📚 OAuth 2.0 개념 정리 (그림으로 이해하기 쉽게)
- [OAuth + Spring Boot + JWT] 4. 스프링 시큐리티없이 OAuth 로그인 구현하기 by. 우테코 3기 에어
- OAuth 2.0 공식문서
- 생활코딩 OAuth2.0