이번 포스팅에서는 구글API를 사용하여 구글 로그인을 구현하는 과정에 대해 정리해보려고 한다.
예전에 소셜로그인을 구현해본 경험이 있지만 세션 기반으로 구현했고 Oauth2 라이브러리를 사용했었다.
하지만 이번 프로젝트에서는 프론트와 백엔드가 나뉘고 Rest API를 만들기 위해 JWT 토큰을 사용해 인증하도록 구현하려 한다.
구글 API를 사용하여 구글로그인하는 과정은 다음과 같다.
구글 로그인버튼을 누르면 `https://accounts.google.com/o/oauth2/v2/auth?client_id=클라이언트 아이디&redirect_uri=리다이렉트 URL&response_type=token&scope=email profile`로 이동한다.
// Login.js
function Login() {
const googleLogin = () => {
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?
client_id=${ process.env.REACT_APP_GOOGLE_CLIENT_ID }
&redirect_uri=${ process.env.REACT_APP_GOOGLE_REDIRECT_URL }
&response_type=token
&scope=email profile`;
};
return (
<button onClick={googleLogin}>구글로그인</button>
);
}
export default Login;
클라이언트 아이디 생성 및 리다이렉트 URL 설정은 아래 공식 문서 참고
https://support.google.com/workspacemigrate/answer/9222992?hl=ko
OAuth 웹 클라이언트 ID 만들기 - Google Workspace Migrate
Google Cloud에서 Google Workspace Migrate 플랫폼의 OAuth 웹 클라이언트 ID를 만듭니다. 웹 클라이언트 ID를 만드는 단계 중요: 2022년 8월 7일 이후에 OAuth 웹 클라이언트를 만드는 경우 Google Workspace Migrate 버
support.google.com
클라이언트 아이디와 리다이렉트 URL은 보안을 위해 .env 파일로 분리했다.
사용자가 로그인에 성공하면 내가 설정해놓았던 리다이렉트 URL로 리다이렉트 된다
리다이렉트 URL 예시
http://localhost:3000/google/callback#access_token=ya29.a0Ad52N3-tiKAZpQBBmSL3x2wl8ㅁDytURewEzMu1I5JPhDfcCziCRAGpb7FmknafUl99pDtBo-BECyAym-NFYYHzXAtvCzoyIFSAcJfwXMVU75ㅁ4pTKgMCN8YIUaUB0iVlOy8dM7y4Q8IBvzEAdTqOMNOAxkTrbAvR3iVhgaCgYKAUUSARASFQHGX2MiYbjNgK0hywCT90cUUrJUpw0169&token_type=Bearer&expires_in=3599&scope=email%20profile%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20openid&authuser=0&prompt=none
리다이렉트된 후 access_token값을 파싱해서 서버로 보내면 된다.
// GoogleLoginProcess.js
import { useNavigate } from 'react-router-dom';
import React, { useEffect } from 'react';
import axios from "axios";
const GoogleLoginProcess = () => {
const navigate = useNavigate();
// 현재 url에서 access_token 추출
const hash = window.location.hash;
const accessToken = hash.substring(hash.indexOf('=') + 1, hash.indexOf('&'));
const handleLoginPost = accessToken => {
const data = {
accessToken: accessToken,
};
try {
const res = axios.post(
"/api/login/googleLogin",
data,
).then(function (res){
const jwt = res.data.jwt;
const userName = res.data.name;
const userEmail = res.data.email;
// 로컬 스토리지에 jwt 토큰 저장
localStorage.setItem("muglog_token", jwt);
// 로그인 성공 페이지로 리다이렉트
const redirectUrl = `/login/success?name=${userName}`;
navigate(redirectUrl);
window.location.reload();
})
} catch (error) {
console.log(error);
}
};
useEffect(() => {
if (accessToken) {
handleLoginPost(accessToken);
} else {
console.log("로그인 재시도하세요.");
}
}, [accessToken, navigate]);
return (
<p>~ 로그인중 ~</p>
);
}
export default GoogleLoginProcess;
access_token이 있다면 유저 정보를 조회하는 것은 매우 간단하다.
'https://www.googleapis.com/oauth2/v1/userinfo?access_token=프론트에서 받은 access_token' 경로로 GET요청하면 다음과 같이 유저 정보를 조회할 수 있다.
다음은 access_token으로 유저 정보 조회 후 멤버 테이블에 INSERT/UPDATE 하고 JWT을 반환하는 메서드다
@PostMapping("/googleLogin")
public ResponseEntity<?> googleLogin(@RequestBody LoginRequest loginRequest){
try{
// 프론트에서 받은 assessToken을 사용해 구글 유저 정보 조회
WebClient client = WebClient.create("https://www.googleapis.com");
Map<String, Object> response = client
.get()
.uri(
uriBuilder ->
uriBuilder
.path("/oauth2/v1/userinfo")
.queryParam("access_token", loginRequest.getAccessToken())
.build()
)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Map.class)
.block();
// 반환값에서 email, name 추출
String email = response.get("email").toString();
String name = response.get("name").toString();
String jwt = "";
// 받아온 email, name을 memberDto에 매핑
MemberDto memberDto = MemberDto.userInfoToMemberDto("google", name, email);
// member정보가 없으면 저장, 있으면 마지막 로그인날짜 업데이트
Long userId = memberService.findMemIdByEmailAndLoginType(email, "google");
if(userId == null) {
userId = memberService.save(memberDto);
} else {
memberService.updateLastLoginDate(userId);
}
if(userId > 0){
jwt = JwtUtil.createJwtToken(userId);
}
Map<String,Object> resMap = new HashMap<>();
resMap.put("jwt", jwt);
resMap.put("name", name);
return new ResponseEntity<>(resMap, new HttpHeaders(), HttpStatus.OK);
}catch (Exception e){
logger.error("Exception", e);
return ResponseEntity.badRequest().build();
}
}
반환받은 JWT는 LocalStorage에 저장하고 헤더에 담아 요청하면 된다.