이번에 맡게된 프로젝트에 카카오로그인 기능 구현하기 총정리!!!
OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다.
사용자가 애플리케이션에게 모든 권한을 넘기지 않고 사용자 대신 서비스를 이용할 수 있게 해주는 HTTP 기반의 보안 프로토콜.
OAuth를 사용하는 서비스 제공자는 대표적으로 구글, 페이스북 등이 있고, 국내에는 대표적으로 네이버와 카카오가 있다.
난 이 중 카카오 로그인을 사용해보겠다.
모든 웹 사이트에서 회원가입 과정을 거치는 것은 사용자에게 부담이 된다.
매번 번거로운 회원가입 과정을 수행해야 할 뿐 아니라, 웹 사이트마다 다른 아이디와 비밀번호를 기억해야 한다.
또한 웹 사이트를 운영하는 측에서도 회원들의 개인정보를 지켜야하는 역할이 부담이 된다. 바이러스와 백신의 관계처럼, 발전하는 해킹 기술을 막기 위해 보안을 강화하는 노력이 지속적으로 필요하기 때문이다.
이런 문제를 해결하기 위해 OAuth2.0 를 사용한 소셜 로그인이 등장했다.
카카오디벨로퍼스 사이트에서 회원가입을 먼저 한 후,
https://developers.kakao.com/console/app

애플리케이션을 추가해준다.

앱 설정 - 일반 에서 만들어준 애플리케이션을 확인할 수 있다.

사이트 도메인 등록을 해야하는데, 나는 일단 localhost:8080으로 등록했고,

바로 밑 탭에서 앱 키를 볼 수 있는데, 나는 이 중 REST API 키만 사용한다.

카카오로 로그인 했을 때 인가토큰을 받게 될 Redirect URI (callback) 를 설정해야한다.

동의항목 설정하기
- 카카오 서버로부터 사용자의 어떤 정보를 받을지 정할 수 있다.
여기서 이메일을 활성화하려면 애플리케이션을 비즈 앱으로 전환해야한다.
앱설정 - 비즈니스 - 개인 개발자 비즈 앱 - 개인 개발자 비즈 앱 전환 진행

계정아래에 Biz 표시가 있으면 정상!
그럼 카카오 서버를 사용할 준비가 끝났으니
Kakao developers에서 제공하는 요청 흐름도이다.
이걸 보면서 연동을 해보자!
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
그리고 이 홈페이지에서 너무 친절하게도 방법을 다 알려주고 있기 때문에 보면서 코드를 작성하면 된다.
@GetMapping("/v1/login/kakao")
public ApiResponse<KakaoLoginResponse> kakaoLogin (@RequestParam String code) throws JsonProcessingException {
return ApiResponse.ok(kakaoService.kakaoLogin(code));
}
일단 위 코드와 같이 소셜로그인하는 api의 컨트롤러를 만들어준 후 (파라미터로는 카카오의 인가코드를 받는다)

코드 작성 순서
1 카카오에서 로그인하면 제공하는 인가코드로 액세스토큰을 요청
2 그 액세스토큰으로 카카오에서 제공하는 사용자의 정보(닉네임, 이메일, 소셜아이디)를 가져온다
3 해당 사용자의 정보로 이 웹사이트에 이미 있는 유저인지 확인하고, 없다면 회원가입을 진행한다.
4 해당 정보를 가지고 토큰을 만들어 반환한다.
위 코드에서 필요한 getToken, getKakaoUserInfo, registerKakaoUserIfNeeded 이 3개의 메서드를 만들어준다.
// 카카오에서 준 인가코드로 액세스토큰을 반환하는 메서드
//kakao developers에서 요청하는대로 http형태를 만들어서 RestTemplete으로 API를 요청
private String getToken(String code) throws JsonProcessingException {
log.info("인가코드 : " + code);
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("https://kauth.kakao.com")
.path("/oauth/token")
.encode()
.build()
.toUri();
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HTTP Body 생성
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", clientId); // REST API 키 - 환경변수 설정
body.add("redirect_uri", "http://localhost:8080/api/auth/v1/login/kakao");
body.add("code", code);
// 위에서 작성한 uri, headers, body로 requestEntity 생성
RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
.post(uri)
.headers(headers)
.body(body);
// 카카오 쪽으로 HTTP 요청 보내고 받는건 String 타입으로 받아옴
ResponseEntity<String> response = restTemplate.exchange(
requestEntity,
String.class
);
// HTTP 응답 (JSON) -> 반환되는 토큰이 String 형태로 되어있어 그 토큰을 파싱
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
return jsonNode.get("access_token").asText();
}
카카오 인가코드로 액세스토큰을 만드는 코드이다.
위 순서 또한 kakaodevelopers 사이트에 잘 나와있고, 카카오에서 필요한 정보를 작성해준 것이다.
요청 URL과 HTTP Header, Body를 작성하고 이를 requestEntity에 담아 요청하고 String 타입으로 받아온다.
그 토큰을 파싱하여 보여준다.
// 파싱 : 파싱은 일련의 문자열로 이루어진 입력을 토큰(Token)이나 트리(Tree)와 같은 구조로 변환하는 과정
중간에 HTTP Body 생성하면서 clientId는 REST API 키이기 때문에 환경변수로 넣어주어야 한다.
// 액세스토큰으로 사용자정보(닉네임, 이메일)을 가져오는 메서드
private KakaoUserDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
log.info("accessToken : " + accessToken);
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("https://kapi.kakao.com")
.path("/v2/user/me")
.encode()
.build()
.toUri();
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// body엔 따로 넣어줄 게 없음
RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
.post(uri)
.headers(headers)
.body(new LinkedMultiValueMap<>());
// HTTP 요청 보내기
ResponseEntity<String> response = restTemplate.exchange(
requestEntity,
String.class
);
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
Long id = jsonNode.get("id").asLong();
String nickname = jsonNode.get("properties")
.get("nickname").asText();
String email = jsonNode.get("kakao_account")
.get("email").asText();
log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
return new KakaoUserDto(id, nickname, email);
}
그 다음 액세스토큰을 사용해 카카오 정보를 가져오는 메서드도 순서는 위 getToken 메서드와 흡사하다.
//해당 유저가 이미 가입되어 있는지 확인하고, 가입되어 있지 않다면 회원가입을 진행하는 메서드
private User registerKakaoUserIfNeeded(KakaoUserDto kakaoUserDto) {
// DB 에 중복된 socialId가 있는지 확인
Long socialId = kakaoUserDto.getId();
User kakaoUser = userRepository.findBySocialId(socialId).orElse(null);
if (kakaoUser == null) {
// 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
String kakaoEmail = kakaoUserDto.getEmail();
User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
if (sameEmailUser != null) {
kakaoUser = sameEmailUser;
// 기존 회원정보에 카카오 Id, 로그인타입 변경
kakaoUser = kakaoUser.updateSocialIdAndLoginType(socialId, LoginType.SOCIAL);
} else {
// 신규 회원가입
// password: random UUID
String password = UUID.randomUUID().toString();
String encodedPassword = passwordEncoder.encode(password);
kakaoUser = new User(kakaoUserDto.getNickname(),
kakaoEmail,
socialId,
encodedPassword,
kakaoUserDto.getUserRole(),
kakaoUserDto.getLoginType());
}
userRepository.save(kakaoUser);
}
return kakaoUser;
}
카카오로 받은 이메일로 유저를 확인하여 없다면 회원가입을 하는 메서드이다.
카카오 로그인으로 회원가입을 진행할 시 패스워드는 UUID(랜덤으로 생성한 문자열)로 설정된다.
여기까지 하면 카카오로그인에 필요한 코드가 끝난다.
인텔리제이를 run한 후 이 형태의 url로 들어가면 카카오로그인 창이 뜬다.
https://kauth.kakao.com/oauth/authorize?client_id={REST API 키}&redirect_uri={설정한 Redirect URI}&response_type=code

그렇게 로그인을 하게 되면 바로 해당 페이지에 jwt Token이 표시되게 구현했다.(이래도 되나 싶었지만 우리는 프론트가 없으니 우선..)

토큰값이 나오고, url에는 인가코드가 표시된다.
이렇게 카카오로그인을 완성!!
했지만 우리 프로젝트에는 회원 정보가 더 필요하기 때문에 카카오로그인한 유저의 추가정보(유저이름, 핸드폰번호, 주소)를 받는 api를 하나 더 구현했다.
@Transactional
public UpdateKakaoUserInfoResponse updateKakaoUserInfo (AuthUser authUser, UpdateKakaoUserInfoRequest updateKakaoUserInfoRequest) {
User kakaoUser = userRepository.findById(authUser.getId())
.orElseThrow(()-> new ApiException(ErrorStatus._NOT_FOUND_USER));
// 카카오 로그인한 사람이 아닐 경우 예외 처리
if (kakaoUser.getLoginType() != LoginType.SOCIAL) {
throw new ApiException(ErrorStatus._PERMISSION_DENIED);
}
kakaoUser.updateUserInfo(updateKakaoUserInfoRequest.getUsername(),
updateKakaoUserInfoRequest.getPhoneNumber(),
updateKakaoUserInfoRequest.getAddress());
userRepository.save(kakaoUser);
return UpdateKakaoUserInfoResponse.of(kakaoUser);
}
그래서 처음 카카오로 회원가입을 하게 되면 추가정보(유저이름, 핸드폰번호, 주소) 이 부분은 null 값으로 들어가게 되고 해당 메서드를 통해 입력을 해야한다.
여기까지 진짜 끝!
출처
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api