상세 컨텐츠

본문 제목

리액트 & 스프링부트 구글로그인 구현 | RestAPI, JWT Token

Project/MugLog

by yooputer 2024. 3. 12. 23:36

본문

이번 포스팅에서는 구글API를 사용하여 구글 로그인을 구현하는 과정에 대해 정리해보려고 한다.

예전에 소셜로그인을 구현해본 경험이 있지만 세션 기반으로 구현했고 Oauth2 라이브러리를 사용했었다.

하지만 이번 프로젝트에서는 프론트와 백엔드가 나뉘고 Rest API를 만들기 위해 JWT 토큰을 사용해 인증하도록 구현하려 한다.


구글 API를 사용하여 구글로그인하는 과정은 다음과 같다.

  1. (프론트) 사용자한테 구글 로그인 페이지를 보여준다.
  2. (사용자) 구글 로그인을 한다. 
  3. (구글) 사용자가 로그인하면 구글은 사용자의 access_token을 담은 url로 리다이렉트 한다.
  4. (프론트) 리다이렉트된 url에서 access_token을 추출한다.
  5. (프론트) 추출한 access_token을 백엔드로 보낸다.
  6. (백엔드, 구글) 프론트에서 받은 access_toekn으로 구글 API를 사용하여 유저 정보를 조회한다.
  7. (백엔드) 유저 정보를 사용해 유저 테이블에 INSERT/UPDATE한다
  8. (백엔드) JWT에 유저 아이디를 담아 프론트로 반환한다.
  9. (프론트) 받은 JWT을 LocalStorage에 저장한다.
  10. (프론트) 앞으로의 모든 Request 헤더에 JWT 토큰을 담아 보낸다. 

사용자에게 구글 로그인 페이지 보여주기 

구글 로그인버튼을 누르면 `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로 리다이렉트 된다

 

리다이렉트 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받아서 JWT 반환하는 API 구현

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에 저장하고 헤더에 담아 요청하면 된다.