2023년 10월 11일에 작성되었고, 블로그를 이전하며 옮겨온 포스트입니다.
여기에서 원본을 확인할 수 있습니다.

지난 글에 이어서 이번 글에서는 Spring Boot에서 카카오 소셜 로그인을 위해 어떤 작업을 해야하는지에 대해 적어보려 한다. Spring Boot에서 카카오 소셜 로그인을 구현하려면 아래와 같은 간단한 과정만 구현하면 된다.
- 프론트엔드에서 전달받은 인가코드로, 접근 토큰 받아오기 (카카오 API 사용)
- 접근 토큰으로 카카오 계정 정보 불러오기 (카카오 API 사용)
- 불러온 카카오 계정 정보로 우리 서비스 회원에 등록하기
- 위 과정에서 2번까지만 구현한다면, 3번의 과정은 기존 JWT를 이용한 회원가입 과정과 동일하다.
인가코드로 접근 토큰 받아오기
프론트엔드에서 인가코드를 받아올 수 있는 카카오 로그인 API를 사용해서 백엔드로 인가코드를 넘겨주었다면, 우리는 받은 인가코드로 접근 토큰을 받아오는 API를 먼저 사용한다. 인가코드로는 카카오 계정에서의 정보를 불러올 수 없고, 접근 토큰을 받아야 카카오 계정에서의 정보를 가져올 수 있기 때문이다. 인가코드로 접근 토큰을 받아오는 API에 관한 문서는 여기에서 확인할 수 있다.
문서를 확인해 보면 아래와 같은 요청이 필요한 것을 알 수 있다. 필수가 아닌 요청 값은 보내줄 필요가 없고, 필수인 값들만 보내주면 된다.
- https://kauth.kakao.com/oauth/token 에 POST 요청
- Header: Content-type: application/x-www-form-urlencoded;charset=utf-8
- Parameter: grant_type에 authorization_code
- Parameter: client_id에 카카오 개발자 센터에서 등록한 앱의 클라이언트 ID 값
- Parameter: redirect_uri에 카카오 로그인 성공 다음 리다이렉트 될 URL 주소
- Parameter: code에 프론트엔드에서 전달받은 인가코드
여기서 주의할 점은 위 내용 중 하나라도 등록된 정보와 일치하지 않거나, 올바르지 않은 값으로 요청한다면 접근 토큰을 받아올 수 없다.
위 내용을 코드로 구현하면 아래와 같다.
SignServiceImpl.java
public String createKakaoToken(String code) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://kauth.kakao.com/oauth/token"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(
"grant_type=" + "authorization_code" +
"&client_id=" + kakaoClientId +
"&redirect_uri=" + kakaoRedirectUri +
"&code=" + code
))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JSONObject jsonObject = new JSONObject(response.body());
return jsonObject.getString("access_token");
}
SignController.java
@PostMapping(value = "/social/kakao")
public ResponseEntity<SignInResultDto> kakaoLogin(@RequestParam String code) throws IOException, InterruptedException {
return ResponseEntity.ok(signService.kakaoLogin(code));
}
/social/kakao 주소로 POST 요청을 보낼 때 파라미터 값으로 인가코드를 넘겨주면, createKakaoToken은 카카오 로그인 API를 호출해서 접근 토큰만 빼내와 값을 반환한다.
접근 토큰으로 카카오 계정 정보 불러오기
이제 인가 코드로 접근 토큰을 가져왔다면, 접근 토큰으로 카카오 계정 정보를 불러올 수 있다. 설정에 따라 불러올 수 있는 값은 달라지는데, 필자는 아래 3가지 정보를 사용자에게 요청했다. 만약 아래 3가지 정보보다 더 필요하다면, Postman이나 API 요청을 보낼 수 있는 툴을 이용해 API 요청을 보내서 JSON 값을 확인하고 아래 코드에서 추가해 사용하면 된다.
- 이름(닉네임)
- 이메일 주소
- 프로필 사진
접근 토큰으로 카카오 계정 정보를 불러오는 API의 문서를 확인해 보면, 아래와 같은 요청이 필요한 것을 확인할 수 있다.
- https://kapi.kakao.com/v2/user/me 에 POST 또는 GET 요청
- Header: Content-type: application/x-www-form-urlencoded;charset=utf-8
- Header: Authorization: Bearer ${ACCESS_TOKEN}
이 API도 마찬가지로, 접근 토큰이 유효하지 않으면 정보를 불러올 수 없다.
위 내용을 코드로 구현하면 아래와 같다.
SignServiceImpl.java
public KakaoUser getKakaoInfo(String accessToken) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://kapi.kakao.com/v2/user/me"))
.header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
.header("Authorization", "Bearer " + accessToken)
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JSONObject jsonObject = new JSONObject(response.body());
JSONObject kakao_account = jsonObject.getJSONObject("kakao_account");
JSONObject profile = kakao_account.getJSONObject("profile");
KakaoUser kakaoUser = new KakaoUser();
kakaoUser.setEmail(kakao_account.getString("email"));
kakaoUser.setNickname(profile.getString("nickname"));
kakaoUser.setProfileImageUrl(profile.getString("profile_image_url"));
return kakaoUser;
}
KakaoUser.java
public class KakaoUser {
private String profileImageUrl;
private String nickname;
private String email;
}
불러온 접근 토큰과 헤더 설정으로 API에 요청을 보낸 값을 KakaoUser라는 객체에 저장하고, KakaoUser 객체를 반환하는 코드다. KakaoUser라는 객체에는 이메일과 닉네임, 프로필 사진 URL을 주입할 수 있다.
불러온 카카오 계정 정보로 서비스 회원에 등록하기
SignServiceImpl.java
public String getRandomPwd() {
String CHAR_LOWER = "abcdefghijklmnopqrstuvwxyz";
String CHAR_UPPER = CHAR_LOWER.toUpperCase();
String NUMBER = "0123456789";
String DATA_FOR_RANDOM_STRING = CHAR_LOWER + CHAR_UPPER + NUMBER;
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < 16; i++) {
int rndCharAt = random.nextInt(DATA_FOR_RANDOM_STRING.length());
char rndChar = DATA_FOR_RANDOM_STRING.charAt(rndCharAt);
sb.append(rndChar);
}
return sb.toString();
}
@Override
public SignInResultDto kakaoLogin(String code) throws RuntimeException, IOException, InterruptedException {
String kakaoUserToken = createKakaoToken(code);
KakaoUser kakaoUser = getKakaoInfo(kakaoUserToken);
// 카카오톡 로그인 시, 이미 가입된 회원이라면
if(userRepository.getByUid(kakaoUser.getEmail()) != null) {
User user = userRepository.getByUid(kakaoUser.getEmail());
user.setRecentLoggedIn(LocalDateTime.now());
userRepository.save(user);
SignInResultDto signInResultDto = SignInResultDto.builder()
.token(jwtTokenProvider.createAccessToken(String.valueOf(user.getUid()), user.getRoles()))
.refreshToken(jwtTokenProvider.createRefreshToken(String.valueOf(user.getUid())))
.uid(user.getUid())
.name(user.getName())
.build();
setSuccessResult(signInResultDto);
return signInResultDto;
} else {
// 카카오 프로필 이미지를 가져올 수 없을 때, 서비스 기본 프로필 사용
if(kakaoUser.getProfileImageUrl() == null) {
kakaoUser.setProfileImageUrl(DEFAULT_PROFILE);
}
if(kakaoUser.getEmail() == null || kakaoUser.getNickname() == null) {
throw new RuntimeException("카카오 정보에서 이메일 또는 닉네임을 가져올 수 없습니다");
}
User user = User.builder()
.uid(kakaoUser.getEmail())
.name(kakaoUser.getNickname())
.createdAt(LocalDateTime.now())
.useAble(true)
.registerType(RegisterType.KAKAO)
.profileUrl(kakaoUser.getProfileImageUrl())
.password(passwordEncoder.encode(getRandomPassword()))
.roles(Collections.singletonList("ROLE_USER"))
.build();
if(!validateUid(user.getUid())) {
throw new RuntimeException("이메일 주소 형식이 아닙니다.");
} else if(userRepository.getByUid(kakaoUser.getEmail()) != null) {
throw new RuntimeException("이미 존재하는 회원입니다.");
} else {
userRepository.save(user);
}
// 회원가입 후 로그인 정보 반환
SignInResultDto signInResultDto = SignInResultDto.builder()
.token(jwtTokenProvider.createAccessToken(String.valueOf(user.getUid()), user.getRoles()))
.refreshToken(jwtTokenProvider.createRefreshToken(String.valueOf(user.getUid())))
.uid(user.getUid())
.name(user.getName())
.build();
setSuccessResult(signInResultDto);
return signInResultDto;
}
}
기존 JWT를 활용한 회원가입 방식과 같다. 약간의 특이사항이 있다면, 소셜 로그인을 할 때에는 보통 비밀번호를 입력하지 않기 때문에 비밀번호에 입력할 값이 없는데, getRandomPassword 함수를 따로 만들어 주어, 영어와 숫자를 섞어 랜덤으로 16자리 비밀번호를 생성해 비밀번호를 설정해 주었다. 어차피 소셜 로그인을 진행할 때에는 비밀번호를 입력하지 않고, 로그인 시에 이메일만 일치하는지 확인하기 때문에 비밀번호는 중요하지 않다.
마무리
이렇게 카카오 로그인을 실제 코드로 구현하는 과정에 대해 적어보았다. 다른 소셜 로그인도 이 카카오 로그인 방식과 동일하게 활용하면 좋을 것 같다. 아직 카카오만 해보고 구글이나 네이버는 안 해봐서 나중에 시도해 보고 또 적어보려 한다.
'Backend > Java' 카테고리의 다른 글
| Spring Boot에서 ChatGPT 활용하기 #2: API로 GPT 응답 값 가져오기 (0) | 2026.08.22 |
|---|---|
| Spring Boot에서 ChatGPT 활용하기 #1: API 연결 테스트 (0) | 2026.08.22 |
| Spring Security: There is no PasswordEncoder mapped for the id "null" 오류 해결 (0) | 2026.08.22 |
| Cookie, Session, Token(JWT) 정리하기 (0) | 2026.08.22 |
| 'Spring Boot + React' 카카오 소셜 로그인 도입하기 (1) (0) | 2026.08.22 |