개인 프로젝트를 하는 중에 당연하게도 OAuth2.0를 이용하여 로그인, 회원가입 등을 구현하는 중에, 뭔가 레퍼런스 혹은 정리본이 있으면 좋겠다고 생각하여 아주 간단한 예제 프로젝트를 생성하여 정리하게 되었다.
이 글에서는 OAuth2.0 작동원리 등 이론적인 내용은 거의 포함하지 않았고, 구현에 초점을 맞추어 진행했습니다.
우선 카카오 OAuth API를 이용하기 위해서는 실제로 카카오 계정으로 로그인을 해야한다. 그러기 위해서 일단 로그인 폼을 가져왔다.
<html>
<head>
<meta charset="UTF-8">
<title>Kakao Login</title>
</head>
<body>
<a href="https://kauth.kakao.com/oauth/authorize?response_type=code&client_id={여기에 REST API KEY를 넣습니다}&redirect_uri=https%3A%2F%2Flocalhost%3A8080%2Foauth">
Kakao 로그인
</a>
</body>
</html>
로그인 폼은 카카오 개발자 사이트에서 제공한다. 위의 코드를 그대로 넣어도 상관없다. 다만 카카오 개발자 사이트에서 애플리케이션을 등록하고 REST API KEY를 Client Id에 집어넣고, 애플리케이션에서 redirect_uri를 지정해주어야 한다.
폼이 생성되면 다음과 같은 화면이 생성된다. 여기서 카카오 계정으로 로그인하면 카카오 서버에 접속하고, 동의항목에 대한 내용이 나온다.
동의항목은 카카오 개발자 사이트에서 애플리케이션을 등록할 때, 내가 원하는 항목을 각각 선택할 수 있다. -> 개발용도로 등록하여 각 항목들을 필수로 선택하게 할 수 있다. 동의를 모두 완료하면 이전에 파라미터로 보냈던 redirect_uri로 인가코드가 포함되어 전송받는다.
이미지가 너무 작아서 안보이겠지만 사실 중요하지 않다. uri 뒤에 ?code={인가코드} 이러한 형식으로 전달된다.
서비스 서버에서는 redirect_uri를 API로 만들어놓고 redirect가 되면 자동호출하게끔 설계할 수 있다.
주의해야 할 사항으로는 redirect_uri를 그냥 넣어서는 안되고, URL Encode를 이용하여 넘겨주어야 한다.
이제는 서비스에서 외부 API 호출을 통해 카카오 서버에서 토큰을 받아보도록 한다.
@Controller
@RequiredArgsConstructor
@Slf4j
public class KakaoController {
private final KakaoTokenJsonData kakaoTokenJsonData;
private final KakaoUserInfo kakaoUserInfo;
private final UserService userService;
@GetMapping("/index")
public String index() {
return "loginForm";
}
@Description("회원이 소셜 로그인을 마치면 자동으로 실행되는 API입니다. 인가 코드를 이용해 토큰을 받고, 해당 토큰으로 사용자 정보를 조회합니다." +
"사용자 정보를 이용하여 서비스에 회원가입합니다.")
@GetMapping("/oauth")
@ResponseBody
public String kakaoOauth(@RequestParam("code") String code) {
log.info("인가 코드를 이용하여 토큰을 받습니다.");
KakaoTokenResponse kakaoTokenResponse = kakaoTokenJsonData.getToken(code);
log.info("토큰에 대한 정보입니다.{}",kakaoTokenResponse);
KakaoUserInfoResponse userInfo = kakaoUserInfo.getUserInfo(kakaoTokenResponse.getAccess_token());
log.info("회원 정보 입니다.{}",userInfo);
userService.createUser(userInfo.getKakao_account().getEmail());
return "okay";
}
}
아주 간단한 예제코드이다. 내용은 유저가 소셜 로그인 페이지에서 로그인을 마치면 자동으로 해당 API로 이동하여 호출된다. Param으로 code를 전달하여 해당 코드를 이용, getToken 함수를 호출하여 토큰 Response를 받게된다.
// 인가코드를 이용하여 Token ( Access , Refresh )를 받는다.
@Component
@RequiredArgsConstructor
public class KakaoTokenJsonData {
private final WebClient webClient;
private static final String TOKEN_URI = "https://kauth.kakao.com/oauth/token";
private static final String REDIRECT_URI = "https://localhost:8080/oauth";
private static final String GRANT_TYPE = "authorization_code";
private static final String CLIENT_ID = "{secret.CLIENT_ID}";
public KakaoTokenResponse getToken(String code) {
String uri = TOKEN_URI + "?grant_type=" + GRANT_TYPE + "&client_id=" + CLIENT_ID + "&redirect_uri=" + REDIRECT_URI + "&code=" + code;
System.out.println(uri);
Flux<KakaoTokenResponse> response = webClient.post()
.uri(uri)
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(KakaoTokenResponse.class);
return response.blockFirst();
}
}
아주 간단한 예제라서 값들을 모두 하드코딩했다. 실제로는 프로젝트 규칙에 맞춰서 작성하면 된다.
토큰을 받기 위해서는 카카오 API를 호출한다. 외부 API를 호출하는 라이브러리는 feignClient, WebClient, RestTemplate 등을 사용할 수 있지만, 예제에서는 WebClient를 이용했다.
Name | Type | Description | Required |
---|---|---|---|
grant_type | String | authorization_code로 고정 | O |
client_id | String | 앱 REST API 키[내 애플리케이션] > [앱 키]에서 확인 가능 | O |
redirect_uri | String | 인가 코드가 리다이렉트된 URI | O |
code | String | 인가 코드 받기 요청으로 얻은 인가 코드 | O |
client_secret | String | 토큰 발급 시, 보안을 강화하기 위해 추가 확인하는 코드[내 애플리케이션] > [보안]에서 설정 가능ON 상태인 경우 필수 설정해야 함 | X |
파라미터 목록은 다음과 같다.
이제 카카오 API를 통해 토큰까지 모두 가져왔다. 이제 이를 이용하여 사용자 정보를 가져온다.
본 예제에서는 Controller에서 getUserInfo 함수를 통해 사용자 정보를 가져온다.
KakaoUserInfoResponse userInfo = kakaoUserInfo.getUserInfo(kakaoTokenResponse.getAccess_token());
아래는 사용자 정보를 가져오는 카카오 API 호출 코드이다.
@RequiredArgsConstructor
@Component
public class KakaoUserInfo {
private final WebClient webClient;
private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me";
public KakaoUserInfoResponse getUserInfo(String token) {
String uri = USER_INFO_URI;
Flux<KakaoUserInfoResponse> response = webClient.get()
.uri(uri)
.header("Authorization", "Bearer " + token)
.retrieve()
.bodyToFlux(KakaoUserInfoResponse.class);
return response.blockFirst();
}
}
AccessToken을 이용한 API 호출은 아래와 같은 헤더와 파라미터로 리턴을 얻을 수 있다.
Name | Description | Required |
---|---|---|
Authorization | 사용자 인증 수단, 액세스 토큰 값Authorization: Bearer ${ACCESS_TOKEN} | O |
Name | Type | Description | Required |
---|---|---|---|
secure_resource | Boolean | 이미지 URL 값 HTTPS 여부, true 설정 시 HTTPS 사용, 기본 값 false | X |
property_keys | String[] | Property 키 목록, JSON Array를 ["kakao_account.email"]과 같은 형식으로 사용 | X |
회원의 AccessToken이외에도 어드민 키를 이용하여 회원 정보를 받을 수 있다. 해당 예제에서는 AccessToekn을 이용한 회원가입만을 다루고 있다.
이제 카카오 회원 정보 가져오기 API를 호출하여 가져왔으니, 이를 가지고 서비스 회원가입을 진행시킨다.
해당 예제에서 회원은 단지 이메일에 대한 정보를 가지고 있다. 예제라서 단순화했다.
컨트롤러에서 userService.createUser(userInfo.getKakao_account().getEmail()); 가져온 이메일 정보를 기반으로 회원을 생성한다.
@Service
@Slf4j
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public Long createUser(String email) {
User user = User.builder()
.email(email)
.build();
userRepository.save(user);
log.info("새로운 회원 저장 완료");
return user.id;
}
}
아주 간단한 회원 생성 예제이다. 프로젝트에서 회원에게 필요한 정보에 맞춰서 카카오 동의항목을 늘리거나 회원에게 추가로 정보를 입력받는 로직을 추가하여 서비스에 맞게 회원가입을 진행할 수 있다.
로컬 환경에서 진행하면 redirect_uri를 https로 설정하여 오류가 발생할 수 있다. 인증서가 없기 때문이다. 로그인을 마치고 api 호출을 http에서 진행하면 문제없이 예제를 진행할 수 있다. 실제 서비스에서는 당연히 https 인증서를 추가하여 진행해야 하기 때문에 문제는 전혀 없다.
전체 코드와 코드에 대한 부가 설명들은 아래에서 확인이 가능하다.