[Spring] 카카오, 구글 OAuth + JWT
아래는 진행중인 프로젝트에서 인증 방식으로 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";
}
댓글남기기