티스토리 뷰

구글 소셜 로그인을 구현하는 방법은 생각보다 여러가지이다. 첫 번째는 OAuth 2.0 를 활용해 백엔드에서 모두 처리(code 생성, 인증)하는 것과 id token (구글은 access token 이 아닌 id token이다) 를 프론트엔드에게 받아 이 id token 을 백엔드에서 인증하는 것이다. 

 

 

나는 그 중에서 id token을 프론트엔드에서 받아와 백엔드에서 인증하는 방식을 설명하고자 한다. 

전체적인 순서는 다음과 같다. 

1. 클라이언트가 구글에게 로그인 요청을 보낸다. 
2. 로그인에 성공하면 구글은 유저의 ID 토큰을 클라이언트에게 넘겨준다.
3. 클라이언트는 서버에 ID 토큰과 함께 로그인 요청을 보낸다.
4. 서버에서는 구글의 토큰 검증 API에 요청을 보내 ID 토큰의 무결성을 검증한다.
5. ID 토큰의 무결성이 검증되면 유저의 이름과 이메일 정보를 추출한다.
6. 구글에서 받은 유저 이름, 이메일 정보를 활용해 Access Token을 생성한다.
7. Access Token을 클라이언트에게 넘겨주며 인증과정을 마친다.

 

여기서 1-3은 프론트엔드에서 처리할 일이기 때문에 생략한다. 

 

- applicaion.properties

app.google.client.id=
jwt.secret=
jwt.expiration=86400000

- 의존성 추가

implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'com.google.api-client:google-api-client:1.32.1'
implementation 'com.google.oauth-client:google-oauth-client:1.32.1'
implementation 'com.google.http-client:google-http-client-jackson2:1.40.1'

- IdTokenVerify

Google ID 토큰의 무결성을 검증하는 클래스이다. 

GoogleIdTokenVerifier 객체를 사용하여 전달된 ID 토큰을 검증하고, 토큰이 유효하다면 사용자 정보를 추출한다. 

@Service
@RequiredArgsConstructor
@Slf4j
public class IdTokenVerfiy {

    private final JwtTokenProvider jwtTokenProvider;

    @Value("${app.google.client.id}")
    private String googleClientId;


    public Map<String, Object> authenticateUser(String idToken) throws GeneralSecurityException, IOException {
        GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance())
                .setAudience(Collections.singletonList(googleClientId))
                .build();

        GoogleIdToken googleIdToken = verifier.verify(idToken);
        if (googleIdToken != null) {
            Payload payload = googleIdToken.getPayload();
            String userId = payload.getSubject();
            String email = payload.getEmail();
            String name = (String) payload.get("name");
            String pictureUrl = (String) payload.get("picture");
            log.info(email);

            String accessToken = jwtTokenProvider.generateToken(email);

            Map<String, Object> userDetails = new HashMap<>();
            userDetails.put("userId", userId);
            userDetails.put("email", email);
            userDetails.put("name", name);
            userDetails.put("pictureUrl", pictureUrl);
            userDetails.put("accessToken", accessToken);

            return userDetails;
        } else {
            throw new IllegalArgumentException("Invalid ID token.");
        }
    }
}

-UserController

validateToken 메소드에서는 IdTokenVerfiy 객체의 authenticateUser 메소드를 호출하여 ID 토큰을 검증하고 사용자를 인증한다. 검증이 성공하면 사용자 정보가 반환되며, 이 정보를 HTTP 응답으로 전송한다.
[예외처리]

  • IllegalArgumentException: 전달된 토큰이 유효하지 않은 경우 발생 / HTTP 상태 코드 401(Unauthorized)
  • GeneralSecurityException 또는 IOException: ID 토큰 검증 중에 예상치 못한 오류가 발생/ HTTP 상태 코드 500(Internal Server Error)
@RestController
@RequiredArgsConstructor
public class UserController {


    private final IdTokenVerfiy idTokenVerfiy;

    @PostMapping("/validateToken")
    public ResponseEntity<?> validateToken(@RequestBody String idToken) {
        try {
            Map<String, Object> userDetails = idTokenVerfiy.authenticateUser(idToken);
            return ResponseEntity.ok(userDetails);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
        } catch (GeneralSecurityException | IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error validating the ID token: " + e.getMessage());
        }
    }
}

- JwtTokenProvider

 

  • generateToken(String subject): 주어진 사용자 subject(예: 이메일)에 대한 JWT 토큰을 생성. 토큰은 발행 시간, 만료 시간 및 HS512 알고리즘을 사용하여 서명된다.
  • getSubjectFromToken(String token): 주어진 JWT 토큰에서 사용자 subject를 추출. 이 메소드는 토큰을 파싱하여 Claims 객체를 얻고, 그 객체에서 사용자 subject를 반환
@Component
public class JwtTokenProvider {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Value("${jwt.expiration}")
    private long jwtExpiration;

    public String generateToken(String subject) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpiration);

        return Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getSubjectFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }
}

참고

 

백엔드 서버로 인증  |  Google Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 백엔드 서버로 인증 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 백엔드 서버와 통신

developers.google.com