3 분 소요

아래는 진행중인 프로젝트에서 인증 방식으로 JWT를 사용하는데 소셜 로그인 이용 시 발급받은 JWT를 그대로 사용하지 않고
백엔드 서버에서 새로 발급한 JWT를 사용하는 방식입니다. 다른 분들이 작성한 글과 내용이 조금 다르니 유의해주세요😀
이 글은 여기에서 설정했던 AppUtils, OAuthService를 그대로 사용합니다.

카카오, 구글 개발자 페이지에서 OAuth 사용 권한 설정 및 중요 정보를 저장하는 내용은 이 페이지에 포함하지 않습니다.

소셜 로그인 HTML

구글, 카카오 소셜 로그인을 테스트하기 위해 html 파일을 만들어주자.
href 설정값에 맞게 API를 컨트롤러에서 정의해주면 된다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>OAuth sign-in</title>
</head>
<body>
<a href="/login/oauth/google">구글 로그인</a>
<a href="/login/oauth/kakao">카카오 로그인</a>
</body>
</html>

카카오

KakaoServiceResponse

카카오 서버에서 토큰을 발급받을 때 응답을 받을 클래스를 정의해주자.

@Getter @Setter
public class KakaoServiceResponse {
    private String token_type;
    private String access_token;
    private String id_token;
    private String scope;
}

OAuthController

카카오 로그인 버튼을 눌렀을 때 인증 페이지로 넘어가도록 설정한 loginKakaoOAuth()
로그인을 시도했을 때 redirect URI에서 처리할 로직을 설정한 kakaoServiceRedirect()이다.
kakaoServiceRedirect() 내에서 API 요청을 1번 더 할 것이므로 RestTemplate를 사용했으며,
Kakao developers에서 얻는 중요 정보들은 application.yml에 저장해 @Value로 불러와 사용하고 있다.

@Controller
@RequiredArgsConstructor
public class OAuthController {

    private final RestTemplate restTemplate;
    private final OAuthService oAuthService;
    private final UserService userService;
    private final JwtTokenProvider jwtTokenProvider;

    @Value("${kakao.client-id}")
    private String KAKAO_CLIENT_ID;
    @Value("${kakao.client-secret}")
    private String KAKAO_CLIENT_SECRET;
    @Value("${kakao.redirect-uri}")
    private String KAKAO_REDIRECT_URI;
    @Value("${kakao.response-type}")
    private String KAKAO_RESPONSE_TYPE;
    @Value("${kakao.scope}")
    private String KAKAO_SCOPE;
    @Value("${kakao.grant-type}")
    private String KAKAO_GRANT_TYPE;

    private final String MAIN_LOGIN_URL = "https://example.com";
    private final String KAKAO_AUTH_URL = "https://kauth.kakao.com/oauth/authorize";
    private final String KAKAO_TOKEN_URL = "https://kauth.kakao.com/oauth/token";

    @GetMapping("/login/oauth/kakao")
    public String loginKakaoOAuth() {
        return "redirect:" + KAKAO_AUTH_URL
                + "?client_id=" + KAKAO_CLIENT_ID
                + "&redirect_uri=" + KAKAO_REDIRECT_URI
                + "&response_type=" + KAKAO_RESPONSE_TYPE
                + "&scope=" + KAKAO_SCOPE;
    }

    @GetMapping("/login/kakao")
    public String kakaoServiceRedirect(@RequestParam String code) {
        URI uri = UriComponentsBuilder.fromUriString(KAKAO_TOKEN_URL)
                .build()
                .toUri();

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", KAKAO_GRANT_TYPE);
        params.add("client_id", KAKAO_CLIENT_ID);
        params.add("client_secret", KAKAO_CLIENT_SECRET);
        params.add("redirect_uri", KAKAO_REDIRECT_URI);
        params.add("code", code);
        KakaoServiceResponse response = restTemplate.postForObject(uri, params, KakaoServiceResponse.class);

        String email = oAuthService.getEmail(response.getId_token());
        User user = userService.findByEmail(email);

        if (user == null) {
            User newUser = oAuthService.saveUser(email, "kakao");
            return "redirect:" + MAIN_LOGIN_URL
                    + "?token=" + jwtTokenProvider.createToken(newUser.getEmail(), newUser.getRoles());
        }
        return "redirect:" + MAIN_LOGIN_URL
                + "?token=" + jwtTokenProvider.createToken(user.getEmail(), user.getRoles());
    }
}
  • 카카오 토큰 발급할 때 application/x-www-form-urlencoded로 요청을 보내야 하므로 LinkedMultiValueMap 사용
  • 응답은 KakaoServiceResponse로 받음

카카오 로그인 인증은 https://kauth.kakao.com/oauth/authorize에서 하므로 여기로 redirect 해주되
client_id, redirect_uri, response_type, scope도 반드시 설정해줘야 한다!
Kakao Developers에서 값을 가져오자.

redirect URI를 /login/kakao로 설정했기 때문에 로그인을 시도하면 kakaoServiceRedirect()를 호출하게 되는데,
로그인에 성공하면 쿼리 스트링으로 code이 넘어오게 된다.

토큰 발급은 https://kauth.kakao.com/oauth/token에서 하므로 uri에 값을 넣어준 뒤
grant_type, client_id, client_secret, redirect_uri, code 등 필요한 값을 같이 넣어서 API 요청을 보낸다.
이 때 code는 위에서 응답으로 받은 code를 사용하고, API 응답은 response로 받는다.

만약 입력한 정보가 DB에 없다면(=유저가 가입되지 않은 상태라면) 가입을 한 뒤 JWT 토큰을 발급해서 redirect 시킨다.
최종적으로 어디로 이동할지는 자유지만 우리 프로젝트에서는 /login에서 쿼리 스트링으로 넘어오는 token 값이 유효한 경우
메인 페이지로 redirect 되도록 설정했기 때문에 다음처럼 설정해줬다.

구글

GoogleServiceResponse

구글 토큰 발급 시 응답을 받아줄 클래스 GoogleServiceResponse를 정의해주자.

@Getter @Setter
public class GoogleServiceResponse {
    private String access_token;
    private Integer expires_in;
    private String scope;
    private String token_type;
    private String id_token;
}

OAuthController

카카오에서 설정한 방식과 똑같이 동작한다. 코드도 똑같다!

@Controller
@RequiredArgsConstructor
public class OAuthController {

    private final RestTemplate restTemplate;
    private final OAuthService oAuthService;
    private final UserService userService;
    private final JwtTokenProvider jwtTokenProvider;

    @Value("${google.client-id}")
    private String GOOGLE_CLIENT_ID;
    @Value("${google.client-secret}")
    private String GOOGLE_CLIENT_SECRET;
    @Value("${google.redirect-uri}")
    private String GOOGLE_REDIRECT_URI;
    @Value("${google.response-type}")
    private String GOOGLE_RESPONSE_TYPE;
    @Value("${google.scope}")
    private String GOOGLE_SCOPE;
    @Value("${google.grant-type}")
    private String GOOGLE_GRANT_TYPE;

    private final String MAIN_LOGIN_URL = "https://fit-tering.com/login";
    private final String GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
    private final String GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";

        @GetMapping("/login/oauth/google")
    public String loginGoogleOAuth() {
        return "redirect:" + GOOGLE_AUTH_URL
                + "?client_id=" + GOOGLE_CLIENT_ID
                + "&redirect_uri=" + GOOGLE_REDIRECT_URI
                + "&response_type=" + GOOGLE_RESPONSE_TYPE
                + "&scope=" + GOOGLE_SCOPE;
    }

    @GetMapping("/login/google")
    public String googleServiceRedirect(@RequestParam String code) {
        URI uri = UriComponentsBuilder.fromUriString(GOOGLE_TOKEN_URL)
                .build()
                .toUri();

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", GOOGLE_GRANT_TYPE);
        params.add("client_id", GOOGLE_CLIENT_ID);
        params.add("client_secret", GOOGLE_CLIENT_SECRET);
        params.add("redirect_uri", GOOGLE_REDIRECT_URI);
        params.add("code", code);
        GoogleServiceResponse response = restTemplate.postForObject(uri, params, GoogleServiceResponse.class);

        String email = oAuthService.getEmail(response.getId_token());
        User user = userService.findByEmail(email);

        if (user == null) {
            User newUser = oAuthService.saveUser(email, "google");
            return "redirect:" + MAIN_LOGIN_URL
                    + "?token=" + jwtTokenProvider.createToken(newUser.getEmail(), newUser.getRoles());
        }
        return "redirect:" + MAIN_LOGIN_URL
                + "?token=" + jwtTokenProvider.createToken(user.getEmail(), user.getRoles());
    }
}

테스트

위에서 설정한 html 파일을 oauth.html이라 한다면, 다음처럼 컨트롤러에 정의해주자.
http://localhost:8080/oauth에서 테스트할 수 있다!

@GetMapping("/oauth")
public String appleLoginPage(ModelMap model) {
    return "oauth";
}

References

카테고리:

업데이트:

댓글남기기