1. 카카오 로그인 과정
1.1. 인가 코드 받기

- 카카오 인증 서버로 /oauth/authorize URI로 GET 요청을 보냄
- 카카오 인증 서버에서 클라이언트에게 카카오 로그인 페이지를 통해 로그인을 요청함
- 사용자는 카카오계정으로 로그인
- 카카오계정이 유효한 경우 카카오 인증 서버에서 클라이언트에게 동의 화면을 통해 사용자 정보 수집 동의를 요청함.
- 클라이언트가 동의한 항목을 카카오 인증 서버에게 요청함.
- 카카오 인증 서버에서는 302 Redirect URI로 서비스 서버에게 인가 코드를 전달
- 여기서 Redirect URI는 Kakao Developers - 내 애플리케이션 - 카카오 로그인에서 추가한 URI로 설정됨
💡 우리는 여기까지가 프론트에서 하는 일이라고 생각!!
1.2. 토큰 요청하기

서비스 서버로 받은 인가 코드를 가지고 사용자 정보에 대한 토큰을 발급받을 수 있음
- 서비스 서버가 Redirect URI로 전달받은 인가 코드로 토큰 받기를 요청(백엔드가 할 일)
- 카카오 인증 서버가 토큰을 발급해서 서비스 서버에 전달
💡 이 토큰은 서비스 서버에서 사용할 액세스 토큰이 아니라는 점에 주의
1.3 사용자 로그인 처리하기

카카오 서버가 해주어야 할 일은 모두 끝났음. 이 부분은 서비스 서버에 직접 구현해야 하는 부분으로 2단계 에서 토큰을 통해 사용자 정보를 받았으니 이 정보를 어떻게 처리할 지 서버에서 구현
2. 서버 구현 카카오 로그인 로직 코드
순서요약
1. (프론트) 카카오로부터 인가코드를 받아옴
2. (프론트) 인가코드를 (백엔드)로 전달
3. (백엔드) 받은 인가코드로 카카오에 토큰을 요청
4. 카카오는 redirect uri, clientid, 인가코드를 검증 후 회원 정보가 담긴 카카오 토큰을 전송
5. (백엔드) 카카오 토큰으로 USER 정보를 get 한 후, 유저를 등록
6. (백엔드) 카카오에서 제공하는 refresh토큰이 아닌, 우리 서버에서 제공하는 자체 JWT Token을 생성하여 (프론트)에 전송
7. (프론트) JWT Token으로 로그인을 처리
2.1 카카오 개발자 페이지 설정

REST API 키 = Client id
2.2. application.yml 설정
kakao:
client: ${KAKAO_CLIENT_ID} # REST-API 키
redirect-uri: ${KAKAO_REDIRECT_URI} # 설정해놓은 redirect-uri
2.3 인가코드 받아오기(프론트)
인가코드
https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}
위 주소로 해당하는 키 값과 uri를 설정한 후 요청을 보내면 인가코드를 얻어올 수 있음
테스트 환경이 아닌 배포 환경에서는 이 redirect URI는 프론트에서 접근할 수 있는 Host로 지정해야 함
위 URI로 요청하게 되면 아래와 같은 정보 요청 동의 페이지가 나오게 됨

- 동의하기를 누르게 되면 이동하는 페이지가 redirect uri
- redirect-uri 뒤에 code={인가코드}

- 프론트는 다음 인가코드 String을 백엔드로 넘겨주면 됨
2.4 인가코드 전달받기
백엔드에서 프론트로부터 인가코드를 전달받는 코드 작성
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auth")
@Tag(name = "Auth", description = "인증 및 로그인 관련 API")
public class AuthController {
private final AuthService authService;
@Operation(summary = "카카오 로그인", description = "인가 코드를 입력 받아서 카카오 로그인을 합니다.")
@GetMapping("/kakao/login")
public ApiResponse<LoginResponseDTO> kakaoLogin(@RequestParam("code") String code) {
return ApiResponse.onSuccess("카카오 로그인 성공", authService.kakaoLogin(code));
}
}
PostMapping이 아닌 GetMapping을 해야함
public class AuthResponseDTO {
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class LoginResponse {
private Long id;
private String name;
private AuthTokens token;
}
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class AuthTokens {
private String accessToken;
private String refreshToken;
}
}
카카오 로그인을 한 다음에 LoginResponse를 프론트에 보낼 것이기 때문에 LoginResponse 를 작성
2.5 전달받은 인가코드로 카카오 서비스에 토큰 요청하기
public interface AuthService {
LoginResponse kakaoLogin(String code);
}
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final KakaoAuthProvider kakaoAuthProvider;
@Override
public LoginResponse kakaoLogin(String code) {
// 1. 인가 코드로 토큰 발급
String accessToken = kakaoAuthProvider.getAccessToken(code);
// 2. 토큰으로 카카오 유저 정보 가져오기
HashMap<String, Object> userInfo = kakaoAuthProvider.getKakaoUserInfo(accessToken);
// 3. 카카오 유저 정보로 로그인
LoginResponse kakaoUserResponse = kakaoAuthProvider.kakaoUserLogin(userInfo);
return kakaoUserResponse;
}
}
@Component
public class KakaoAuthProvider {
private final MemberRepository memberRepository;
private final RefreshTokenService refreshTokenService;
private final JwtTokenProvider jwtTokenProvider;
@Value("${kakao.client}")
private String clientId;
@Value("${kakao.redirect-uri}")
private String redirectUri;
public String getAccessToken(String code) {
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);
body.add("redirect_uri", redirectUri);
body.add("code", code);
// HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = null;
try {
response =
restTemplate.exchange(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
kakaoTokenRequest,
String.class);
} catch (Exception e) {
throw new GlobalException(GlobalErrorCode.KAKAO_AUTH_ERROR);
}
// HTTP 응답 (JSON) -> 액세스 토큰 파싱
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = null;
try {
jsonNode = objectMapper.readTree(responseBody);
} catch (JsonProcessingException e) {
throw new GlobalException(GlobalErrorCode.KAKAO_AUTH_ERROR);
}
return jsonNode.get("access_token").asText(); // 토큰 추출
}
}
2.6 카카오 토큰으로 USER 정보를 가져온 후, USER 등록하기
KakaoAuthProvider에 아래와 같은 메소드들도 추가
@Component
public class KakaoAuthProvider {
public HashMap<String, Object> getKakaoUserInfo(String accessToken) {
HashMap<String, Object> userInfo = new HashMap<String, Object>();
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> kakaoUserInfoRequest = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response =
restTemplate.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
kakaoUserInfoRequest,
String.class);
// responseBody에 있는 정보 추출
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = null;
try {
jsonNode = objectMapper.readTree(responseBody);
} catch (JsonProcessingException e) {
throw new GlobalException(GlobalErrorCode.KAKAO_AUTH_ERROR);
}
Long id = jsonNode.get("id").asLong();
String email = jsonNode.get("kakao_account").get("email").asText();
String nickname = jsonNode.get("properties").get("nickname").asText();
userInfo.put("id", id);
userInfo.put("email", email);
userInfo.put("nickname", nickname);
return userInfo;
}
public LoginResponse kakaoUserLogin(HashMap<String, Object> userInfo) {
Member kakaoUser = memberRepository.findByEmail(userInfo.get("email").toString()).orElse(null);
Member newMember;
if (kakaoUser == null) {
newMember = memberRepository.save(KakaoAuthConverter.toMember(userInfo));
} else {
newMember = kakaoUser;
}
String accessToken = jwtTokenProvider.generateAccessToken(newDeveloper.getId());
String refreshToken = jwtTokenProvider.generateRefreshToken(newDeveloper.getId());
refreshTokenService.saveRefreshToken(refreshToken);
return KakaoAuthConverter.toLoginResponse(accessToken, refreshToken, newMember);
}
}
2.7 부가 구현물
요청 DTO를 Entity로 변환하는 경우나 Entity를 응답 DTO로 변환하는 경우의 재사용성을 위해 Converter를 작성.
auth/Converter 디렉터리 아래에 작성
@Component
public class KakaoAuthConverter {
public static Member toMember(HashMap<String, Object> userInfo) {
return Member.builder()
.email((String) userInfo.get("email"))
.nicName((String) userInfo.get("nickname"))
.build();
}
public static LoginResponse toLoginResponse(String accessToken, String refreshToken, Member member) {
return LoginResponse.builder()
.id(member.getId())
.name(member.getNicName())
.token(AuthToken.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build()).build();
}
}
로그인 테스트를 위해 Member Entity, Repository, Service 작성
@Entity
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
@Column(columnDefinition = "varchar(30)")
private String nicName;
@Column(columnDefinition = "varchar(50)")
private String email;
}
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
public interface MemberService {
}
@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
}
'Dev > SpringBoot' 카테고리의 다른 글
카카오 로그인 구현하기(3) - Redis + 액세스 토큰 재발급하기 (0) | 2025.02.10 |
---|---|
카카오 로그인 구현하기(2) - JWT + Spring Security (0) | 2025.02.10 |
SpotlessApply 및 Pre-Commit (0) | 2025.02.10 |
Redis를 이용하여 Write Back 구현하기 (0) | 2025.02.10 |
SpringBoot에서 Redis 연동하기 (0) | 2025.02.10 |