Web Programming

구글 로그인 쉽게 구현하기 3편 - 로그인 구현하기 (SpringBoot + Vue.js)

놋수저 2023. 1. 24. 11:30
반응형

로그인 처리 플로우는 아래와 같다.

인가 코드 받기 -> 토큰 받기 -> 사용자 정보 가져오기
해당 글에서는 인가 코드 받기는 클라이언트 페이지에서, 토큰 받기사용자 정보 가져오기는 서버에서 처리할 예정이다.
위 플로우 대로 처리되기 위해 2개의 클라이언트 페이지와 1개의 API가 필요하다.
1. 클라이언트 페이지

  • 구글 로그인 버튼 페이지
  • 구글 로그인 후 Redirect되는 페이지

2. 서버

  • 클라이언트 페이지에서 전달받은 인가코드를 통해 로그인 토큰 발급 및 유저 정보를 조회하는 API

구글 인증 구현

1. 인가 코드 받기
서비스 서버가 구글 인증 서버로 인가 코드 받기를 요청한다. 여기서 얻은 인가 코드는 로그인 토큰을 얻기 위해 사용된다.
클라이언트 페이지에서 구글 로그인창이 노출되고 구글 계정으로 로그인한 후 앞서 설정한 Redirect URL로 redirect될 때 인가코드를 확인할 수 있다.

2. 토큰 받기
앞서 얻은 인가 코드로 로그인 토큰 발급을 요청한다. 토큰 받기를 마쳐야 구글 로그인을 정상적으로 완료할 수 있다. 해당 토큰으로 로그인이 유효한지 판단할 수 있으며 각 토큰의 역할과 만료 시간에 대한 자세한 정보는 토큰 정보에서 확인할 수 있다. 액세스 토큰의 경우, 토큰 정보 보기로 토큰 유효성을 검사하거나 리프레시 토큰을 사용해 갱신할 수 있다.

3. 사용자 정보 가져오기
앞서 얻은 액세스 토큰을 헤더에 담아 사용자 정보를 가져올 수 있다.

위 과정 외에도 토큰 갱신, 로그아웃 등의 추가적으로 제공하는 API가 있지만 해당 글에서는 로그인 구현을 위해 필수로 사용해야하는 API만 사용할 예정이다.

인가 코드 받기

구글 로그인창을 노출시키는 방법은 앞서 구글 개발자센터에서 발급받은 Key와 Redirect URL 등을 파라미터에 추가하여 호출하면 된다.

doGoogleLogin() {
  const url = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=' +
      process.env.VUE_APP_GOOGLE_CLIENT_ID +
      '&redirect_uri=' +
      process.env.VUE_APP_GOOGLE_REDIRECT_URL +
      '&response_type=code' +
      '&scope=email profile';

  this.showSocialLoginPopup(url)
},

위 함수는 구글 로그인창을 호출하는 함수이다.
구글 로그인창을 호출하는 URL은 https://accounts.google.com/o/oauth2/v2/auth 이다.
필수로 추가해야하는 파라미터는 response_type, client_id, redirect_uri, state 총 4개가 있다.

  • client_id: 구글 개발자센터에서 발급받은 JavaScript Key
  • redirect_uri: 구글 개발자센터에서 설정한 Callback URI
  • response_type: 'code'로 고정 (인가코드를 통한 로그인 방식)
  • scope: 토큰 발급 이후 유저 정보에서 어떤 항목을 조회할 것인가를 띄어쓰기를 구분으로 하여 ' ' 입력해준다.

요청문 샘플

https://accounts.google.com/o/oauth2/v2/auth?client_id=372120948172-9iioibgeotln9303ic988kb57nsr8shp.apps.googleusercontent.com&redirect_uri=http://localhost:8080/google-login&response_type=code&scope=email profile



요청값을 올바르게 설정했을 경우 구글 로그인 페이지가 노출된다.


동의항목 설정을 완료하면 앞서 구글 개발자 센터에서 설정한 Redirect URL로 구글 Redirect 해준다.
로그인 성공 후 Redirect URL은 아래와 같이 설정된다.

http://localhost:8080/google-login?code=4%2F0AWtgzh5gVWlJ_FBu1lbJuVY5cZEKrnFG-xuVF6CEv5t9PaV4qzUipbzYAEYJYkq6opmSPg&scope=email%20profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20openid&authuser=0&prompt=consent


http://localhost:8080/google-login은 구글 개발자센터에서 설정한 Callback URL이며 그 뒤에는 code라는 이름의 파라미터가 존재한다. 여기서 code를 이용해 로그인 처리에 필요한 토큰을 얻을 수 있다.

로그인 토큰 얻기

인가 코드는 로그인 토큰을 얻기 위한 코드일 뿐 로그인 처리가 완료된 것이 아니다. 로그인 토큰까지 발급되어야 정상적으로 로그인 처리가 완료된다.
앞서 클라이언트 페이지에서 얻은 인가 코드를 통해 로그인에 필요한 토큰을 얻을 수 있다.
인가 코드를 통해 로그인 토큰을 가져오는 API 이다.

@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping("/social-login")
    public ResponseEntity<LoginResponse> doSocialLogin(@RequestBody @Valid SocialLoginRequest request) {

        return ResponseEntity.created(URI.create("/social-login"))
                .body(userService.doSocialLogin(request));
    }


구글 인증 서버에서 로그인 토큰을 가져온다.
요청 URI은 https://oauth2.googleapis.com/token 이다.
로그인 토큰을 가져오기 위해 아래 정보를 Body에 담아 요청한다.

  • code : 클라이언트 페이지에서 얻은 인가 코드를 사용 하기 때문에 "code"로 고정.
  • client_id : 구글 개발자센터에서 발급 받은 Client ID
  • client_secret : 구글 개발자센터에서 발급 받은 Client Secret
  • redirect_uri : 구글 개발자센터에서 등록한 redirect_uri
  • grant_type: 'authorization_code' 로 고정 (인가코드를 통한 로그인 방식)
@FeignClient(value = "googleAuth", url="https://oauth2.googleapis.com", configuration = {FeignConfiguration.class})
public interface GoogleAuthApi {
    @PostMapping("/token")
    ResponseEntity<String> getAccessToken(@RequestBody GoogleRequestAccessTokenDto requestDto);
}


정상적으로 응답받았을 때의 응답 데이터는 아래와 같다.

{
  "access_token": "ya29.a0AVvZVsoM7NcpWadtvBxeZ08TBWLgSRTalp-okqU7OE-60I7y55YFhwsyYtb9vgzM3kowLty1olKZu5VT1-IQRHRm5Q7zGJd4M9KwDMrIBl-Lm520AqTEGPoJj2A49a79qBoHM5I2FvmvbcSdECFO-DRSUPB5aCgYKAcgSARASFQGbdwaIItciccgK62Fq1q2-gRDXvA0163",
  "expires_in": 3599,
  "scope": "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid",
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImFmYzRmYmE2NTk5ZmY1ZjYzYjcyZGM1MjI0MjgyNzg2ODJmM2E3ZjEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIzNzIxMjA5NDgxNzItOWlpb2liZ2VvdGxuOTMwM2ljOTg4a2I1N25zcjhzaHAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIzNzIxMjA5NDgxNzItOWlpb2liZ2VvdGxuOTMwM2ljOTg4a2I1N25zcjhzaHAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDM3MDc5NDY3ODE3MTc5NDk1OTQiLCJlbWFpbCI6ImRzZHM1NTBAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJsTTdYZGpNaW5FYzFtaGgtcXJtMVlRIiwibmFtZSI6IuycpOyEse2YuCIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BRWRGVHA1T1c0Q0l0MTNlZzVLNmo2djRNYTAzQm0xZGd0d0tscFAzUFVvNz1zOTYtYyIsImdpdmVuX25hbWUiOiLshLHtmLgiLCJmYW1pbHlfbmFtZSI6IuycpCIsImxvY2FsZSI6ImtvIiwiaWF0IjoxNjc0NTI2Mjc3LCJleHAiOjE2NzQ1Mjk4Nzd9.ECZHKrc7l87qLIao43EAFWrT6rTH9uSBz2u3RpKLOhjgwLxPYQby_cZTqSdKhn-4M7VO9NVjS_Aeowe3g0SXYbjCXd_e9HnzU-kSoCzku97Gc1_aBB2uOECimDwu00Kl7p7wHbVXWjczJ8eIDDKHlM5_ZJKahzlR4dp-ir2xsgm0egth2OJumDsNjtC5MgVubjkfgtFanfnOwqaJ43Ko_C5PQ6e49ee0wh-N4srI2cK2QmXhQEbNITI0JSV9DZUIPM3_8ePSlsb0ElQ1hvQ__PY26uSdAg_ZpSiJZjS5B4YJPILz4s1sUEYZpcxVg8P-T7YTCbj3rjBq8PJec1IL3g"
}
  • access_token: 사용자 액세스 토큰 값
  • refresh_token: 사용자 리프레시 토큰 값
  • scope: 조회하고자 하는 사용자 정보
  • token_type: 토큰 타입, Bearer로 고정
  • expires_in: 액세스 토큰의 만료시간 (초)

위 과정을 통해 로그인 처리를 완료하였다. 토큰의 만료시간이 정해져 있으므로 필요에 따라 토큰 갱신 API를 이용하면 된다.
여기서 얻은 accss_token으로 앞서 설정한 동의 항목을 바탕으로 로그인한 유저의 정보를 가져올 수 있다.

유저 정보 가져오기

위 과정에서 발급 받은 액세스 토큰으로 유저 정보를 가져올 수 있다.
구글 서버에서 유저 정보를 가져온 후 필요에 따라 만들고자 하는 서비스에서 회원가입 또는 로그인 처리를 할 수 있다.
유저 정보 조회 API는 파라미터에 발급받은 액세스 토큰을 셋팅하여 요청하면 된다.

요청 URI는 https://www.googleapis.com/userinfo/v2/me 다.

@FeignClient(value = "googleUser", url="https://www.googleapis.com", configuration = {FeignConfiguration.class})
public interface GoogleUserApi {
    @GetMapping("/userinfo/v2/me")
    ResponseEntity<String> getUserInfo(@RequestParam("access_token") String accessToken);
}


정상적으로 응답받았을 때의 응답 데이터는 아래와 같다.

{
  "id": "103707946781717949594",
  "email": "dsds550@gmail.com",
  "verified_email": true,
  "name": "윤성호",
  "given_name": "성호",
  "family_name": "윤",
  "picture": "https://lh3.googleusercontent.com/a/AEdFTp5OW4CIt13eg5K6j6v4Ma03Bm1dgtwKlpP3PUo7=s96-c",
  "locale": "ko"
}


구글 유저 정보를 응답받은 이후에는 구현하고자 하는 서비스 로직에 따라 원하는 코드를 추가하면된다.

if (userRepository.findByUserId(socialUserResponse.getId()).isEmpty()) {
    this.joinUser(
            UserJoinRequest.builder()
                    .userId(socialUserResponse.getId())
                    .userEmail(socialUserResponse.getEmail())
                    .userName(socialUserResponse.getName())
                    .userType(request.getUserType())
                    .build()
    );
}
 


구글도 충분히 문서가 잘 정리되어 있지만 네이버, 카카오가 워낙 더 잘 되어있어 구현하는데 시간이 조금 더 걸렸다.
소셜로그인 API는 처리방식이 다 동일하여 첫 연동만 잘하면 이후 연동은 문제 없을 듯 하다.

아래는 SNS 로그인 구현한 소스코드 링크이다. 카카오, 네이버 로그인도 구현되어 있으니 소셜로그인 연동 개발 하시는 분들은 참고하셔도 좋을 것 같다

소스코드

https://github.com/vvsungho/social-login-view

 

GitHub - vvsungho/social-login-view: 소셜 로그인 클라이언트 화면

소셜 로그인 클라이언트 화면. Contribute to vvsungho/social-login-view development by creating an account on GitHub.

github.com

https://github.com/vvsungho/social-login-server

 

GitHub - vvsungho/social-login-server: 소셜 로그인 서버

소셜 로그인 서버. Contribute to vvsungho/social-login-server development by creating an account on GitHub.

github.com

 

관련글

https://notspoon.tistory.com/45

 

구글로그인 쉽게 구현하기 1편 - Google Developers 설정

구글 로그인 API 클라이언트 입장에서 수많은 사이트의 모든 아이디 비밀번호를 기억하기는 쉽지 않다. 또한 서비스를 제공해주는 리소스 오너 또한 안전하게 보관하여야 하기 때문에 부담된다.

notspoon.tistory.com

https://notspoon.tistory.com/46

 

구글로그인 쉽게 구현하기 1편 - Google Developers 설정

구글 로그인 API 클라이언트 입장에서 수많은 사이트의 모든 아이디 비밀번호를 기억하기는 쉽지 않다. 또한 서비스를 제공해주는 리소스 오너 또한 안전하게 보관하여야 하기 때문에 부담된다.

notspoon.tistory.com

반응형
LIST