본문 바로가기
Project/모면

[Spring Boot] Security 없이 OAuth2 Google에서 받은 id_token 사용해 회원 정보 저장하기

by 독서왕뼝아리 2023. 4. 3.

2023.03.27 - [Project/모면] - [Spring Boot] Security 없이 OAuth2로 Google 로그인 구현, 유저 정보 얻기

이전 시간에 access_token을 발급받아오는 작업까지 마쳤다. 이제 id_token 안의 정보를 복호해 <내 서버의 회원>으로 만들어버리자.

 


 

먼저 RestTemplate을 이용해 POST 요청을 보내는 코드를 수정했다.

 

ResponseEntity<GoogleOAuthResponseDto> responseEntity = restTemplate
        .postForEntity(GOOGLE_TOKEN_URL, params, GoogleOAuthResponseDto.class);
package com.momyeon.backend.dto;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class GoogleOAuthResponseDto {
    private String access_token;
    private String expires_in;
    private String scope;
    private String token_type;
    private String id_token;
}

 postForEntity 메서드의 세 번째 파라미터를 CustomDto.class로 변경했다. JSON 형식의 String을 response body를 바로 클래스 객체로 역직렬화하게 했다. 여기서 회원 정보를 가지고 있는 field는 id_token이다.

 

 

 

Google 공식 문서에서 알 수 있듯이 id_token은 Base64Url로 암호화 되어있다.

 

 

import io.jsonwebtoken.impl.Base64UrlCodec;

// ...

public String decryptBase64UrlToken(String jwtToken) {
    byte[] decode = new Base64UrlCodec().decode(jwtToken);
    return new String(decode, StandardCharsets.UTF_8);
}

수많은 디코딩 방식이 존재하지만 "io.jsonwebtoken:jjwt" 의존성의 디코딩 방법을 선택했다. 가장 깔끔하기 때문.

 

 


 

여기서 한 가지 문제점이 발생했다. 

 

JWT 토큰은 {header}.{payload}.{signature} 형식으로 구성되어 있다.

 

 

그래서 그런지 디코딩하면 heaer는 정상적으로 디코딩 되는데 payload 부분은 제대로 해독되지 않는 현상이 발생했다.

 

 

String decodedInfo = decryptBase64UrlToken(responseEntity.getBody().getId_token().split("\\.")[1]);

그래서 완성된 코드이다. 헤더 부분이 필요 없어서 split("\\.") 코드로 짤라버렸다. 뭐.. 나중에 필요한 경우가 생긴다면... 미래의 내가 하겠지.... 몰랐는데 온점(.)도 이스케이프 문자가 필요하다고 한다. 

 

 

private MemberInfoDto transJsonToMemberInfoDto(String json) {
    try {
        ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MemberInfoDto dto = mapper.readValue(json, MemberInfoDto.class);

        saveMemberInfo(dto);
        return dto;
    } catch (JsonMappingException e) {
        throw new RuntimeException(e);
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }
}

MemberInfoDto로 파싱해주고!

 

 

JSON 파싱하는 건 다음 글에서 확인 가능하다. (๑╹o╹)✎

2023.04.02 - [Backend/Java] - [Java] String to Json 파싱하기 | Jackson ObjectMapper

 

 

 

public void saveMemberInfo(MemberInfoDto dto) {
    Optional<Member> member = memberRepository.findById(dto.getSub());
    member.ifPresentOrElse(null, () -> memberRepository.save(dto.toEntity()));
}

MemberId가 Repository에 없을 때 저장하도록 구현하였다.

 

DB에 저장되는 것도 확인됐다!