[Java] Spring Boot: 카카오 로그인 기능 추가하기
이전 내용
[Java] Spring Boot: 구글 로그인 기능 추가하기
이전 내용 전반적인 순서1. Gradle 또는 Maven 의존성 추가2. 구글 API Console 설정 - 구글 개발자 콘솔 프로젝트 생성 → OAuth 2.0 클라이언트 ID 생성3. application.yaml 설정 (보안 문제로 해당 파일 .gitignore
puppy-foot-it.tistory.com
카카오 로그인 기능 추가하기 소개
이전 시간에는 구글 로그인 기능을 추가하였으므로, 이번에는 카카오 로그인 기능도 추가해 본다.
카카오 로그인은 카카오계정으로 다양한 서비스에 로그인할 수 있도록 하는 OAuth 2.0 기반의 소셜 로그인 서비스다.
카카오 로그인 사용자는 별도의 회원가입 없이 카카오톡이나 카카오계정으로 서비스에 로그인할 수 있다. 서비스 제공자는 사용자의 개인정보를 직접 저장하고 관리할 필요 없이, 카카오 API 플랫폼으로부터 검증된 사용자 인증 정보를 안전하게 전달받아 활용할 수 있다.
상세 절차 및 내용은 하단의 링크(카카오 디벨로퍼)를 참고하면 더 큰 도움이 될 수 있다.
https://developers.kakao.com/docs/latest/ko/kakaologin/common
Kakao Developers
카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.
developers.kakao.com
REST API 방식의 카카오 로그인 구현
다음은 카카오 디벨로퍼 사이트에 나와있는 카카오 로그인 과정을 나타낸 시퀀스 다이어그램이다.
(보다 자세한 내용은 https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api 에서 확인)
Step1. 인가 코드 받기
- 서비스 서버가 카카오 인증 서버로 인가 코드 받기를 요청.
- 카카오 인증 서버가 사용자에게 인증을 요청.
- 사용자 클라이언트에 유효한 카카오계정 세션이 있거나, 카카오톡 인앱 브라우저에서의 요청인 경우 4단계로 넘어감.
- 사용자가 카카오계정으로 로그인.
- 카카오 인증 서버가 사용자에게 동의 화면을 출력하여 인가를 위한 사용자 동의를 요청. ▶ 동의 화면은 서비스 앱(애플리케이션)의 동의항목 설정으로 구성.
- 사용자가 필수 동의항목과, 이 외의 원하는 동의항목에 동의한 뒤 [동의하고 계속하기] 버튼을 누름.
- 카카오 인증 서버는 서비스 서버의 Redirect URI로 인가 코드를 전달.
Step2. 토큰 받기
- 서비스 서버가 Redirect URI로 전달받은 인가 코드로 토큰 받기를 요청.
- 카카오 인증 서버가 토큰을 발급해 서비스 서버에 전달.
Step3. 사용자 로그인 처리 (서비스에서 자체 구현 필요)
- 서비스 서버가 발급받은 액세스 토큰으로 사용자 정보 가져오기를 요청해 사용자의 회원번호 및 정보를 조회하여 서비스 회원인지 확인.
- 서비스 회원 정보 확인 결과에 따라 서비스 로그인 또는 회원 가입.
- 이 외 서비스에서 필요한 로그인 절차를 수행한 후, 카카오 로그인한 사용자의 서비스 로그인 처리를 완료.
카카오 로그인 추가하기
먼저 카카오 디벨로퍼에 회원가입을 해야 한다. (카카오 계정으로 바로 가입 가능)
로그인 후, [시작하기] 버튼 클릭
[애플리케이션 추가하기] 버튼을 눌러 애플리케이션을 생성한다.
그리고나서 앱 설정 > 플랫폼 - Web 플랫폼 등록 클릭
사이트 도메인을 등록 후, Redirect URI를 등록한다.
활성화 설정을 한 후, Redirect URI를 설정.
제품 설정 > 카카오 로그인 > 동의항목 에서는 카카오 로그인 시, 카카오의 어떤 정보를 받아 사용할 것인지를 설정하는 부분으로, 필자의 경우 현재는 로그인 서비스만 구현해볼 것이기 때문에 시간 관계상 패스.
https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite
▶ 동의항목은 필수이므로, 미리 설정하고 넘어가야 한다.
앱 설정 > 앱 키에 들어가서 네이티브 앱 키와 REST API 키를 복사하여 메모해 둔다.
카카오 클라이언트 ID와 SecretKey 발급받아
properties에 추가하기
application.properties에 들어가서 하단의 내용을 추가한다.
(오류가 해결된 최종 버전이므로, 해당 버전으로 추가하면 된다.)
spring.security.oauth2.client.registration.kakao.client-name=kakao-account
spring.security.oauth2.client.registration.kakao.client-id=[카카오 클라이언트 id]
spring.security.oauth2.client.registration.kakao.client-secret=[카카오 클라이언트 SecrectKey]
spring.security.oauth2.client.registration.kakao.scope=profile, account_email
spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:9091/login/oauth2/code/kakao
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id
spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post
카카오 클라이언트 ID는 앞서 받은 앱 키에서 REST API 키를 사용하면 되고,
SecretKey는 카카오 로그인 > 보안에서 발급 받을 수 있다.
카카오 로그인 버튼 이미지
하단의 링크에서 [리소스 다운로드] 클릭
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
[전체 다운로드(.zip)] 클릭하여 이미지 다운로드
다운 받은 파일의 압축을 풀고, 이 중 마음에드는 파일을 하나 골라서
main / resources / static / img 파일에 옮겨놓고 파일명을 kakao로 변경
그리고 기존의 oauthLogin.html 파일에서 구글 로그인 구현 밑에 카카오 로그인 버튼 이미지를 넣는 코드를 넣어준다.
<div class = "mb-2">
<a href="/oauth2/authorization/google">
<img src="/img/google.png">
</a>
<a href="/oauth2/authorization/kakao">
<img src="/img/kakao.png">
</a>
</div>
※ 필자의 경우, 카카오 버튼이 구글 버튼 사이즈에 비해 커서 카카오 버튼 사이즈를 조정해 주었다.
또한, OAuth2SuccessHandler.java 파일 역시 수정해주었다.
package com.example.spro01.config.oauth;
import com.example.spro01.config.jwt.TokenProvider;
import com.example.spro01.domain.RefreshToken;
import com.example.spro01.domain.User;
import com.example.spro01.repository.RefreshTokenRepository;
import com.example.spro01.service.UserService;
import com.example.spro01.util.CookieUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
@RequiredArgsConstructor
@Component
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
// 리프레시 토큰 쿠키의 이름
public static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken";
// 리프레시 토큰 유효 기간 (14일)
public static final Duration REFRESH_TOKEN_DURATION = Duration.ofDays(14);
// 액세스 토큰의 유효 기간(1일)
public static final Duration ACCESS_TOKEN_DURATION = Duration.ofDays(1);
// 인증 성공 후 리다이렉트할 경로
public static final String REDIRECT_PATH = "/articles";
// JWT 토큰 생성을 위한 TokenProvider
private final TokenProvider tokenProvider;
// 리프레시 토큰을 저장하기 위한 리포지토리
private final RefreshTokenRepository refreshTokenRepository;
// OAuth2 인증 요청 관련 쿠키를 관리하는 리포지토리
private final OAuth2AuthorizationRequestBasedOnCookieRepository authorizationRequestRepository;
// 사용자 정보를 조회하기 위한 서비스
private final UserService userService;
// OAuth2 인증 성공 시 호출
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// 인증된 사용자의 OAuth2User 객체 가져오기 *principal: 인증된 사용자 정보를 가지고 있음
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
// 사용자 이메일 속성을 사용해 사용자 정보 조회
String email = extractEmail(oAuth2User);
if (email == null) {
throw new IllegalArgumentException("Email not found in OAuth2 user attributes");
}
User user = userService.findByEmail(email);
// 리프레시 토큰 생성 -> 저장 -> 쿠키에 저장
String refreshToken = tokenProvider.generateToken(user, REFRESH_TOKEN_DURATION); // 유효 기간 14일
saveRefreshToken(user.getId(), refreshToken);
addRefreshTokenToCookie(request, response, refreshToken);
// 액세스 토큰 생성 -> 패스에 액세스 토큰 추가
String accessToken = tokenProvider.generateToken(user, ACCESS_TOKEN_DURATION); // 유효기간 1일
String targetUrl = getTargetUrl(accessToken);
// 인증 관련 설정값, 쿠키 제거
clearAuthenticationAttributes(request, response);
// 클라이언트를 지정된 URL로 리다이렉트
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
private String extractEmail(OAuth2User oAuth2User) {
// 카카오 계정에서 이메일 추출
if (oAuth2User.getAttributes().containsKey("kakao_account")) {
Map<String, Object> kakaoAccount = (Map<String, Object>) oAuth2User.getAttributes().get("kakao_account");
if (kakaoAccount != null && kakaoAccount.containsKey("email")) {
return (String) kakaoAccount.get("email"); // 이메일 값을 올바르게 반환
}
}
// 구글 계정에서 이메일 추출
if (oAuth2User.getAttributes().containsKey("email")) {
return (String) oAuth2User.getAttributes().get("email");
}
// 네이버 계정에서 이메일 추출
if (oAuth2User.getAttributes().containsKey("response")) {
Map<String, Object> responseAttributes = (Map<String, Object>) oAuth2User.getAttributes().get("response");
return (String) responseAttributes.get("email");
}
return null; // 이메일을 찾을 수 없는 경우 null 반환
}
// 생성된 리프레시 토큰을 전달받아 데이터베이스에 저장
private void saveRefreshToken(Long userId, String newRefreshToken) {
RefreshToken refreshToken = refreshTokenRepository.findByUserId(userId)
// 기존 토큰 있으면 업데이트
.map(entity -> entity.update(newRefreshToken))
// 없으면 새 RefreshToken 객체 생성
.orElse(new RefreshToken(userId, newRefreshToken));
// 리프레시 토큰 저장
refreshTokenRepository.save(refreshToken);
}
// 생성된 리프레시 토큰을 쿠키에 저장
private void addRefreshTokenToCookie(
HttpServletRequest request, HttpServletResponse response, String refreshToken) {
int cookieMaxAge = (int) REFRESH_TOKEN_DURATION.toSeconds();
CookieUtil.deleteCookie(request, response, REFRESH_TOKEN_COOKIE_NAME);
CookieUtil.addCookie(response, REFRESH_TOKEN_COOKIE_NAME, refreshToken, cookieMaxAge);
}
// 인증 관련 설정값, 쿠키 제거
private void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
super.clearAuthenticationAttributes(request);
authorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
}
// 액세스 토큰을 패스에 추가
private String getTargetUrl(String token) {
return UriComponentsBuilder.fromUriString(REDIRECT_PATH)
.queryParam("token", token)
.build()
.toUriString();
}
}
그리고 실행하면,
동의 항목 설정하기
필자의 경우, 앞서 동의항목을 설정하지 않았더니 잘못된 요청 에러가 발생했다.
다시 카카오 로그인 - 동의항목에 들어가서 동의항목 설정에서 항목을 확인해 본다.
필자의 경우, 구현하는 앱이 비즈앱이 아니라서 심사를 받아야 하는데, 어려우므로 profile_nickname만 사용하도록 한다.
profile_nickname을 '필수 동의'로 바꾸고, 동의 목적을 적고
application.properties에서 scope(범위)를 profile_nickname으로 변경 후 서버를 다시 실행하면
spring.security.oauth2.client.registration.kakao.scope=profile_nickname
카카오 로그인이 잘 설정된 것을 확인할 수 있다.
카카오 동의항목 추가
(개인 개발자 비즈 앱 전환)
필자가 구현한 앱에서는 이메일 정보가 추가돼야 하기 때문에,
동의 항목에 이메일 등의 항목을 추가해줘야 한다.
그렇기 위해서는 비즈 앱 전환을 해야 하는데, 다행히도 사업자 등록증이 없는 개인 개발자도 비즈 앱 전환이 가능하다.
카카오 디벨로퍼에 로그인 - 내 애플리케이션에서 먼저 앱 아이콘을 등록하고
- 비즈니스 - 비즈 앱 정보
로 들어간 다음 [개인 개발자 비즈앱 전환] 클릭하여 사용하고자 하는 비즈 앱 전환 목적을 선택하고 전환을 누르면
전환이 완료된다.
그리고 application.properties에서 scope에도 추가해준다.
spring.security.oauth2.client.registration.kakao.scope=profile_nickname, account_email
[참고]
https://imweb.me/faq?mode=view&category=29&category2=47&idx=71729
https://mini-min-dev.tistory.com/121
다음 내용
[Java] Spring Boot: 네이버 로그인 구현하기
이전 내용 [Java] Spring Boot: 카카오 로그인 기능 추가하기이전 내용 [Java] Spring Boot: 구글 로그인 기능 추가하기이전 내용 전반적인 순서1. Gradle 또는 Maven 의존성 추가2. 구글 API Console 설정 - 구글 개
puppy-foot-it.tistory.com