-
OAuth와 Kakao, Google 로그인 API 곁들이기Protocol/OAuth 2024. 8. 7. 21:53
들어가기 전에
인증 서비스 프로젝트를 진행하던 중 우리가 자주 볼 수 있는 카카오 or 구글 로그인이 내부적으로 어떻게 동작되는지 알아보기 위해 구현한 내용을 작성한다.
OAuth 의미
OAuth(Open Authorization)은 액세스 위임을 위한 개방형 표준 인가 프로토콜이다.
다시말해, 내가 가지고 있는 자원(resource)을 누군가 요청했을 때 A만 허락할지, A ~ F 까지 허락할지 정해주는 것이다.
인증(Authentication)과 인가(Authorization)
여기서 하나 더 알고 가야될 것 인증, 인가의 차이점이다.
여러가지 해석이 있지만 이해하기 쉬운 해석으로 풀어보자면, 은행을 방문한다고 가정하자.
은행원: 안녕하세요! 홍길동님. 무엇을 도와드릴까요?
홍길동(나): 대출하러 왔습니다.
은행원: 먼저, 본인 확인부터 할게요. 신분증 보여주세요.
홍길동(나): (신분증 제시함)
은행원: 본인 확인 되셨습니다. 얼마나 대출하시나요?
홍길동(나): 5천만원이요.
은행원: 대출 가능 여부 확인해보겠습니다.
대출 가능 여부 확인중....
은행원: 홍길동님은 대출이 1천만원 가능합니다.
홍길동(나): 아니! 왜요?
은행원: 신용 점수가 너무 낮으시네요 ^^위 대화흐름에서 신분증 확인이 인증이다. 먼저 대출을 하기전에 당신이 우리 고객이 맞는지 확인하는 것
대출 가능 여부 확인 과정이 인가이다. 당신이 우리 고객인건 알겠는데 신용점수(대출조건)가 낮아서 1천만원 까지만 대출이 허가되는 것
여기까지 알아보았으니 다시 구글 개발자 문서를 기반으로 시나리오를 구성해보자. (카카오 문서도 유사하다)
시나리오
구글 Cloud API 문서에서는 5개의 시나리오를 제공한다.
- 웹 서버 애플리케이션
- 설치된 애플리케이션
- 클라이언트 측(자바스크립트) 애플리케이션
- 입력이 제한된 기기의 애플리케이션
- 서비스 계정
여기서는 웹 서버 애플리케이션 시나리오로 설명한다. (나머지 시나리오도 중간 과정에 차이가 있을 뿐 큰 흐름은 똑같다)
google oauth flow 시나리오 흐름은 아래와 같다.
- Google Cloud Platform Console 에서 승인 사용자 인증 정보를 만든다.
- Console 에서 생성한 앱에 엑세스 범위를 식별한다. (실제 구현전에 앱에서 액세스 권한이 필요한 범위를 정하는 것)
- 엑세스 토큰을 가져온다.
- 승인(인가) 코드 발급받을 매개변수 설정
- Google OAuth 2.0 서버로 Redirection
- Google 에서 사용자에게 동의 요청 메시지 표시
- OAuth 2.0 서버 응답 처리
- 승인 코드를 갱신 토큰 및 액세스 토큰으로 교환
- 사용하고 싶은 Google API 호출
- 사용자 계정을 대신하여 토큰 사용
- 쿼리 매개변수 또는 HTTP 헤더에 access_token 포함 (HTTP 헤더 사용 권장.)
HTTP 헤더 사용 예.
GET /drive/v2/files HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {access_token}
쿼리 매개변수 사용 예.
GET https://www.googleapis.com/drive/v2/files?access_token={access_token}Tip. 사용자 동의 요청 메시지 표시
코드 흐름
Spring Boot를 사용해 구현됨.
Controller -> Service -> Redirect View
- 고객이 google login 버튼을 누르면 /google-login 컨트롤러 매핑되어 goGoogleOAuth() 실행
- AuthorizationCodeFlow 객체에 Builder 생성 및 승인 코드 매개변수 설정 후 객체 반환
- newAuthorizationUrl() 승인 코드 발급 요청
- Redirection View /google-login-callback 컨트롤러 매핑되어 googleLoginCallback(String) 실행
- newTokenRequest(String) 엑세스 토큰 발급 요청
- 발급된 액세스 토큰은 Base64로 인코딩된 JSON 객체인 JWT
- Base64url 인코딩된 값을 디코딩하고 내부 JSON을 파싱해 사용자 정보 사용
@RequestMapping("/google-login") public RedirectView goGoogleOAuth() { return googleService.goGoogleOAuth(); } @RequestMapping("/google-login-callback") public RedirectView googleLoginCallback(@RequestParam("code") String code){ return googleService.loginCallback(code); }
구현 중 보게 된 오류
Base64url로 인코딩된 값을 디코딩 하는데 발생한 오류와 google api endpoint 이슈를 만나게 되었다.
1. java.lang.1IllegalArgumentException: Illegal base64 character 20
2. java.lang.IllegalArgumentException: Illegal base64 character 2e
3. Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: java.net.UnknownHostException: apitest.acme.com] with root cause오류 1번은 base64로 구성된 문자 중 (공백) ASCII 값이 발견되어 발생하는 오류이다.
-> base64Url을 base64로 변환할 때 공백을 제거했다. String.replace(" ", "");
오류 2번은 base64로 구성된 문자 중 .(닷) ASCII 값이 발견되어 발생하는 오류이다.
-> 문자열을 .(닷)을 기준으로 분리해주었다. String.split("\\.");
오류 3번은 api endpoint (apitest.acme.com) 주소를 알 수 없다는 오류이다.
-> google api token endpoint를 구글 문서에서 확인해 변경했다. https://oauth2.googleapis.com/token
추가 이슈.
Base64Url은 내부 문자열 구성에 '-'(대시), '_'(언더바)를 포함하고 있는데 Base64 변환시 해당 기호를 해석하지 못한다.
그래서 String.replace("-","+") 와 String.replace("_","/")로 먼저 교체하고 Base64로 변환 해야한다.
Base64 인코딩에서는 문자열의 길이가 4의 배수가 되어야 하므로, 부족한 패딩을 채워줘야한다.