서론
지난 포스트에 이어 스프링 프로젝트에서 리액트-스프링 부트간의 REST API를 통한 카카오 로그인과 카카오 회원가입 구현에 대해 소개하겠다.
본론
카카오 회원가입
카카오 서버로부터 정보를 받아와서 서비스에 정보를 등록해두고 회원가입 처리를 해준다.
프론트에서 인가 코드를 받아 서버로 전달
프론트 과정 설명으로 다루겠다.
프론트엔드에서 인가 코드를 발급받고 redirect uri로 리다이렉트되었을 때, 쿼리 파라미터에 담긴 code를 서버로 전달해주어야 한다.
위의 api를 통해 인가 코드를 발급받는다.
위의 3가지 쿼리 파라미터를 작성하고 GET 요청을 보내면 카카오 로그인 화면이 나타나고 로그인을 완료하면 설정한 redirect uri로 리다이렉트된다.
redirect uri는 내 애플리케이션 → 카카오 로그인 → Redirect URI에 등록해줘야 한다.
인가 코드를 받은 서버는 인가 코드를 사용하여 카카오에 요청하여 토큰을 발급받는다.
위에서 발급받은 코드는 프론트에서 서버로 전달하게 되고 서버는 해당 코드를 통해 카카오로부터 토큰을 발급받는다.
위의 URL로 grant_type, client_id, redirect_uri, code, client_secret을 쿼리 파라미터로 담아서 POST 요청을 보내면 서버는 토큰을 발급해준다.
정리하면 다음과 같다.
- https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={REST_API 키}&redirect_uri={위에서 코드를 발급받을 때 사용한 redirect_uri}&code={발급받은 인가 코드}&client_secret={이전 포스트에서 발급받은 client_secret}
하나하나 차근차근 살펴보자.
grant_type = authorization_code
위의 값은 고정이다. 그대로 사용하면 된다.
client_id = {REST_API 키}
내 애플리케이션에 들어가면 요약 정보에 앱 키가 나와있는데 4가지 앱 키들 중에서 REST API 키를 사용하면 된다.
redirect_uri = {위에서 코드를 발급받을 때 사용한 redirect_uri}
프론트엔드에서 사용자에게 카카오 로그인 페이지를 보여줄 때 카카오에게 요청을 보낸다.
그때 사용한 redirect_uri와 동일한 주소를 사용해주어야 요청이 똑바로 진행된다.
code = {발급받은 인가 코드}
프론트엔드로부터 인가 코드를 받았을 것이다. 그 인가 코드를 그대로 사용하면 된다.
client_secret = {이전 포스트에서 발급받은 client_secret}
내 애플리케이션 → 카카오 로그인의 세부탭 “보안” → 코드
Client_secret을 발급받고 활성화 먼저 해주어야 한다.
발급받은 토큰을 사용해서 유저 정보 가져오기
위에서 발급받은 토큰을 헤더에 담아서 위의 URL로 요청을 날리면 동의항목에 설정한 정보를 담아서 보내준다.
카카오 회원가입 end-point와 DTO
그럼 여기까지 설명했던 내용을 스프링으로 구현해보자.
@PostMapping("/kakao/signup")
public ResponseEntity<UserDTO> kakaoSignUp(
@RequestBody KakaoDTO kakaoDTO
){
try {
return ResponseEntity.status(201).body(this.authService.kakaoSignUp(kakaoDTO));
}catch (Exception e){
e.printStackTrace();
return ResponseEntity.status(400).body(null);
}
}
/kakao/signup으로 사용자의 요청을 받는다.
프로젝트에서 카카오에서 받는 정보 + 필요한 정보로 회원가입을 진행해야 했기 때문에 추가적인 데이터를 받아주기 위해 kakaoDTO를 만들어서 사용했다.
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class KakaoDTO {
private String code;
private String name;
private String phone;
private String role;
}
DTO는 위와 같다.
카카오 회원가입 서비스 로직
@Override
public UserDTO kakaoSignUp(KakaoDTO kakaoDTO) throws URISyntaxException {
// 1. code를 사용하여 카카오 서버에서 토큰을 발급받는다.
String kakaoAccessToken = this.fetchKakaoAccessToken(kakaoDTO.getCode(), true);
// 2. 토큰을 사용하여 카카오 서버에서 유저의 정보를 가져온다.
LinkedHashMap<String, Object> value = this.fetchKakaoUserData(kakaoAccessToken);
String email = (String) value.get("email");
value = (LinkedHashMap<String, Object>) value.get("profile");
// 3. 카카오에서 받아온 유저 정보와 회원가입으로 얻은 정보를 취합하여 회원가입을 진행한다.
UserDTO userDTO = UserDTO.builder()
.name(kakaoDTO.getName()).role(kakaoDTO.getRole()).phone(kakaoDTO.getPhone())
.email(email).nickname((String) value.get("nickname")).password("KAKAO").build();
return this.signUp(userDTO);
}
먼저 3가지 절차로 나누어 생각했다.
- code를 사용하여 카카오 서버에서 토큰을 발급받는다.
- 토큰을 사용하여 카카오 서버에서 유저의 정보를 가져온다.
- 카카오에서 받아온 유저 정보와 회원가입으로 얻은 정보를 취합하여 회원가입을 진행한다.
code를 사용하여 카카오 서버에서 토큰을 발급받는다.
private String fetchKakaoAccessToken(String code, boolean redirect) throws URISyntaxException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("grant_type", this.grant_type);
parameters.add("client_id", this.client_id);
parameters.add("redirect_uri", redirect ? redirect_uri_signup : redirect_uri_signin);
parameters.add("code", code);
parameters.add("client_secret", this.client_secret);
HttpEntity<?> http = new HttpEntity<>(parameters, headers);
URI uri = new URI(this.kakaoOauthUri);
ResponseEntity<LinkedHashMap> response = restTemplate.exchange(uri, HttpMethod.POST, http, LinkedHashMap.class);
return "Bearer "+response.getBody().get("access_token");
}
위에서 설명한 내용들을 토대로 RestTemplate 구성요소들을 만들어주고 요청을 보내면 다음과 같은 형태의 json이 카카오 서버로부터 온다.
위의 정보를 파싱하기 위해서는 LinkedHashMap 형태로 만들어주고 거기서 우리가 필요한 정보인 access_token을 Bearer와 붙여서 반환한다.
토큰을 사용하여 카카오 서버에서 유저의 정보를 가져온다.
private LinkedHashMap<String, Object> fetchKakaoUserData(String kakaoAccessToken) throws URISyntaxException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.set("Authorization", kakaoAccessToken);
HttpEntity<?> http = new HttpEntity<>(headers);
URI uri = new URI(this.kakaoDataUri);
ResponseEntity<LinkedHashMap> response = restTemplate.exchange(uri, HttpMethod.GET, http, LinkedHashMap.class);
return (LinkedHashMap<String, Object>) response.getBody().get("kakao_account");
}
위에서 발급받은 토크을 Authorization 헤더에 담아주고 카카오 서버에 요청을 보내면 동의항목에 정의된 내용을 기반으로 데이터가 온다.
많은 정보들이 json안에 json 형태로 있기 때문에 LinkedHashMap으로 캐스팅해가면서 필요한 정보를 알맞게 파싱하면 된다.
// 3. 카카오에서 받아온 유저 정보와 회원가입으로 얻은 정보를 취합하여 회원가입을 진행한다.
UserDTO userDTO = UserDTO.builder()
.name(kakaoDTO.getName()).role(kakaoDTO.getRole()).phone(kakaoDTO.getPhone())
.email(email).nickname((String) value.get("nickname")).password("KAKAO").build();
return this.signUp(userDTO);
이렇게 받은 정보들과 필요한 정보들을 취합해서 기존의 회원가입 절차와 동일하게 처리해주었다.
로그인 end-point
로그인 절차는 회원가입과 크게 다른 부분은 없다.
@PostMapping("/kakao/signin")
public ResponseEntity<TokenDTO> kakaoSignin(
@RequestBody KakaoDTO kakaoDTO
){
try {
return ResponseEntity.status(200).body(this.authService.kakaoSignIn(kakaoDTO));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(400).body(null);
}
}
여기서 kakaoDTO을 사용하여 정보를 받았지만 실제로 로그인에서 사용하는 값은 code만 사용하였다.
카카오 로그인 서비스 로직
public TokenDTO kakaoSignIn(KakaoDTO kakaoDTO) throws URISyntaxException {
// 1. code를 사용하여 카카오 서버에서 토큰을 발급받는다.
String kakaoAccessToken = this.fetchKakaoAccessToken(kakaoDTO.getCode(), false);
// 2. 토큰을 사용하여 카카오 서버에서 유저의 정보를 가져온다.
LinkedHashMap<String, Object> value = this.fetchKakaoUserData(kakaoAccessToken);
String email = (String) value.get("email");
// 3. 카카오에서 받아온 유저 이메일이 서버에 존재하는지 확인하고 비밀번호가 KAKAO인지 체크하여 토큰을 발급한다.
this.existEmailCheck(email);
UserEntity userEntity = userDAO.readUser(email);
this.passwordCheck("KAKAO", userEntity.getPassword());
return this.makeToken(userEntity.getEmail(), userEntity.getRoles().get(0));
}
1번 2번까지는 회원가입과 동일하다.
1번과 2번과정을 통해 카카오 토큰을 발급하고 해당 토큰으로 받은 유저의 이메일이 우리 서비스에 존재한다면 인증 처리가 완료되었다고 생각하면된다.
3번은 카카오에서 받아온 유저 이메일이 서버에 존재하는지 확인하고 비밀번호가 KAKAO인지 체크한 후 토큰을 우리 서비스에서 사용하는 토큰을 발급해준다.
- 여기서 비밀번호가 KAKAO인지 체크해주는 이유는 카카오로 회원가입된 유저는 비밀번호가 없기 때문에 KAKAO를 비밀번호로 넣어두어 카카오 유저인지 체크해주었다.
- 실제 로그인시에는 비밀번호가 8자리 이상 입력해야하기 때문에 보안상 위험은 적다.
'Spring' 카테고리의 다른 글
[Spring Boot] Spring Boot에서 JPA QueryDSL 적용 방법 (0) | 2024.04.17 |
---|---|
[Spring Boot] 도커 컨테이너에 환경에서 application.yml 민감한 정보 환경변수로 묶어내기 (0) | 2024.04.04 |
[Spring] 스프링 프로젝트에 카카오 로그인, 회원가입 구현 - (1) (0) | 2024.03.31 |
[Spring] Spring boot 프로젝트를 javadoc 문서로 만들기 (0) | 2024.03.29 |
[Spring Security] 스프링 부트 Access Token에서 Refresh Token추가하여 구현하기 (0) | 2024.03.27 |