반응형
해당 글에서는 'Spring Security의 사용 없이' Kakao 로그인을 구성하는 방법에 대해 공유합니다.
💡 [참고] 해당 글은 이전의 글에서 이어지는 내용입니다.
1) 개발 환경
개발 환경 | 버전 |
java | 11 |
Spring Boot | 2.7.5 |
Spring Boot Starter WebFlux | 2.7.5 |
spring-boot-starter-thymeleaf | 2.7.5 |
thymeleaf-layout-dialect | 3.1.0 |
빌드관리도구 | Gradle 7.5 |
개발 툴 | IntelliJ IDEA 2022.3 |
API 테스트 툴 | Postman |
2) 환경 구성 : Kakao 기본 환경 설정
1. 카카오 로그인 과정
2. Kakao developers 내에 애플리케이션을 추가합니다.
3. 카카오 로그인을 활성화합니다.
💡 카카오 로그인을 할 수 있도록 활성화를 수행합니다.
💡 '제품 설명 > 카카오 로그인 > 활성화 설정 > 활성화'를 통해서 카카오톡 로그인에 대한 활성화를 수행합니다.
4. 플랫폼을 등록합니다.
💡 Web 플랫폼을 등록합니다.
💡 '앱 설정 탭 > 플랫폼 > Web > Web 플랫폼 등록' 버튼을 누릅니다.
http://localhost:8000
5. Redirect URI를 지정합니다.
💡 API 호출을 수행하였을 때 카카오 로그인이 출력되도록 Redirect URI를 지정합니다
💡 '제품 설명 > 카카오 로그인 > Redirect URI > 페이지'를 입력하고 저장합니다.
http://localhost:8000/api/v1/oauth2/kakao
6. Client Scecret을 지정합니다.
💡 보안을 강화하기 위해 Client Secret을 발급받고 활성화합니다.(REST API 경우에만 설정이 가능합니다)
💡 '제품 설명 > 카카오 로그인 > 보안 > 코드 생성' 버튼을 누릅니다.
💡 최초 활성화가 안되어 있으므로 활성화를 시켜줍니다.
7. 카카오톡 로그인을 통해서 가져올 정보에 대해서 선택합니다.
💡카카오톡 로그인을 수행할 때 사용자에게 요구할 정보들에 대해서 설정합니다.
💡 '제품 설명 > 카카오 로그인 > 동의항목' 탭을 선택하고 가져올 범위에 대해서 선택합니다.
8. 카카오 로그인 테스트
💡 REST_API_KEY와 REDIRECT_URI을 포함시켜서 호출하였을 때 카카오톡 로그인 페이지가 출력이 됩니다.
💡 REST_API_KEY는 ‘앱 설정’ > ‘요약정보’ > ‘REST API 키’를 복사해서 붙여 넣으면 되고, REDIRECT_URI의 경우는 방금 전에 추가한 리다이렉션 URI를 넣습니다.
https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri={REDIRECT_URI}&response_type=code
# {REST_API_KEY} : ‘앱 설정’ > ‘요약정보’ > ‘REST API 키’ 내에서 확인이 가능합니다.
# {REDIRECT_URI} : 위에서 지정한 REDIRECT_URL을 참고하시면 됩니다.
9. 리다이렉트 페이지 확인
💡 ‘전체 동의’를 선택하고 ‘동의하고 계속하기’ 버튼을 누르면 지정한 redirect_uri로 이동함을 확인되었습니다.
3) 구성하기
💡 해당 페이지 구성을 위해 아래와 같은 순서도를 기반으로 프로세스를 구성하였습니다.
💡 해당 부분에서는 아래의 과정을 통해 최종적으로 카카오톡 로그인을 통해 사용자 정보를 조회하는 과정으로 구성을 하였습니다.
1. 사용자가 ‘카카오 로그인’ 버튼을 클릭합니다.
2. 카카오 로그인 화면이 출력됩니다.
3. 로그인 화면에서 ‘동의하고 계속하기’ 버튼 클릭합니다.
4. 인증 이후 ‘인가 코드’를 발급받습니다. 리소스 서버 요청 (https://kauth.kakao.com/oauth/authorize)
5. 리다이렉션 URI로 이동됩니다.
6. ‘인가코드’로 ‘접근 토큰(Access Token)’ 발급받습니다. 리소스 서버 요청(https://kauth.kakao.com/oauth/token)
7. ‘접근 토큰’으로 로그인된 ‘사용자 정보를 조회’ 합니다. 리소스 서버 요청(https://kapi.kakao.com/v2/user/me)
1. 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // Thymeleaf Template
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.1.0' // Thymeleaf Layout
implementation 'org.springframework.boot:spring-boot-starter-webflux:2.7.13' // Spring Webflux
}
💡 [참고] Thymeleaf에 대한 초기 설정이 필요하시면 아래의 글을 참고하시면 도움이 됩니다.
- 간단한 화면이기에 사용 중인 플랫폼으로 UI를 구현하시면 될 것 같습니다.
2. 주요 사용 API 문서
💡 해당 글에서 사용하는 API에 대해서 확인해 봅니다.
분류 | HTTP Method | URL | 필수 파라미터 | 설명 |
인가코드 발급 | GET | https://kauth.kakao.com/oauth/authorize | client_id redirect_uri response_type |
카카오 로그인 동의 화면을 호출하고, 사용자 동의를 거쳐 인가 코드를 발급합니다. |
토큰 발급 | POST | https://kauth.kakao.com/oauth/token | grant_type client_id redirect_uri code |
이전에 발급 받은 인가코드를 기반으로 토큰을 발급받습니다. |
사용자 정보 조회 | GET/POST | https://kapi.kakao.com/v2/user/me | Authorization Content-type |
현재 로그인한 사용자의 정보를 불러옵니다. |
[참고] API 문서
3. UI 화면 및 인가코드 받기
💡 간단한 UI를 구성하고 버튼을 눌렀을 때 인가코드를 받도록 구성합니다.
3.1. 간단한 로그인 페이지와 로그인 페이지를 불러오는 Controller를 구성합니다.
💡 ‘카카오톡 로그인’을 클릭하였을 때 해당 링크를 통해 카카오톡 로그인 화면이 나오도록 구성이 되었습니다.
<!DOCTYPE html>
<!-- Default Layout Import-->
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{common/layouts/defaultLayout}"
layout:fragment="Content"
>
<head>
<title></title>
<!-- IN-line JS 영역-->
<script th:inline="javascript" type="text/javascript">
</script>
</head>
<body>
<div>
<div>로그인</div>
<a href="https://kauth.kakao.com/oauth/authorize?client_id=KEY&redirect_uri=http://localhost:8000/api/v1/oauth2/kakao&response_type=code">카카오톡 로그인</a>
</div>
</div>
</body>
</html>
💡 로그인 페이지를 불러오는 @Controller입니다.
/**
* 로그인 페이지를 불러옵니다.
* @param model
* @return
*/
@GetMapping("/login")
public String login(Model model) {
model.addAttribute("title", "");
return "pages/login/loginPage";
}
3.2. 카카오에서 지정한 리다이렉트 API를 구성합니다.
💡 해당 API는 카카오톡 로그인을 하였을 때 리다이렉트 되는 API입니다. 간단하게 파라미터로 code라는 반환값을 받아서 ‘Access Code’를 확인하도록 구성하였습니다.
/**
* 카카오 로그인 redirect uri
*
* @return ResponseEntity<ApiResponse>
* @link
* @since 2023.07.15
*/
@GetMapping("/kakao")
public ResponseEntity<ApiResponse<Object>> kakaoLogin(@RequestParam String code) {
String answer = "";
log.debug("[+| Kakao Login AccessToken :: " + code);
ApiResponse<Object> ar = ApiResponse
.builder()
.result(answer)
.resultCode(SUCCESS_CODE)
.resultMsg(SUCCESS_MSG).build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
3.3. 중간 구성 화면
💡 로그인 화면
💡 ‘카카오톡 로그인’을 눌렀을 때 페이지
💡 ‘확인하고 계속하기’ 버튼을 눌렀을 때 페이지
4. 토큰 및 사용자 정보 조회
💡 인가 코드로 ‘토큰’을 생성하고 해당 토큰을 통해서 사용자 정보를 조회하는 과정입니다.
1. RequestParam의 code 형태로 인가 코드를 반환받습니다.
2. 인가 코드를 통해서 토큰을 생성합니다.
3. 생성된 토큰에는 접근 코드(Access Code)가 있는데 이를 통해서 Header에 이를 추가하여 사용자 정보를 조회합니다.
4. 최종적으로 사용자 조회를 확인합니다.
4.1. RequestParam의 code 형태로 인가 코드를 반환받습니다.
💡 해당 API는 카카오톡 로그인을 하였을 때 리다이렉트 되는 API입니다.
💡 간단하게 파라미터로 code라는 반환값을 받아서 ‘Access Code’를 확인하도록 구성하였습니다.
/**
* 카카오 로그인 redirect uri
*
* @return ResponseEntity<ApiResponse>
* @link
* @since 2023.07.15
*/
@GetMapping("/kakao")
public ResponseEntity<ApiResponse<Object>> kakaoLogin(@RequestParam String code) {
String answer = "";
log.debug("[+| Kakao Login AccessToken :: " + code);
ApiResponse<Object> ar = ApiResponse
.builder()
.result(answer)
.resultCode(SUCCESS_CODE)
.resultMsg(SUCCESS_MSG).build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
4.2. ‘인가코드’를 기반으로 토큰을 생성합니다.
💡 토큰을 발급받기 위해 필수적으로 들어가는 요소를 구성하여 WebClient를 이용하여 요청을 합니다.
package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.common.codes.SuccessCode;
import com.adjh.multiflexapi.common.codes.constans.MainConstants;
import com.adjh.multiflexapi.common.response.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Spring Boot OAuth 2 Client 구성
*
* @author : jonghoon
* @fileName : OAuth2Controller
* @since : 2023/07/09
*/
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/oauth2")
public class OAuth2Controller {
final int SUCCESS_CODE = SuccessCode.SELECT.getStatus();
final String SUCCESS_MSG = SuccessCode.SELECT.getMessage();
final String MAIN_TAG = MainConstants.MAIN_TAG;
final String CLIENT_ID = "XXXX";
final String CLIENT_SECRET = "XXXX";
final String GRANT_TYPE = "authorization_code";
/**
* 카카오 로그인을 동의 하였을때 처리 이후 redirect 되는 API
*
* @return ResponseEntity<ApiResponse>
* @link
* @since 2023.07.15
*/
@GetMapping("/kakao")
public ResponseEntity<ApiResponse<Object>> kakaoLogin(@RequestParam String code) {
// [STEP1] 인가 코드를 받습니다.
log.debug("[+| Kakao Login AccessToken :: " + code);
// [STEP2] 인가 코드를 기반으로 토큰을 발급받습니다.
JSONObject tokenInfo = generateToken(code);
String accessToken = String.valueOf(tokenInfo.get("access_token"));
int refreshTokenExpiresIn = (int) tokenInfo.get("refresh_token_expires_in");
String refreshToken = String.valueOf(tokenInfo.get("refresh_token"));
String scope = String.valueOf(tokenInfo.get("scope"));
ApiResponse<Object> ar = ApiResponse
.builder()
.result("")
.resultCode(SUCCESS_CODE)
.resultMsg(SUCCESS_MSG).build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* 인가 코드를 통해서 토큰을 발급받습니다.
*
* @param {String} code 인가코드
* @return {JSONObject}
*/
private JSONObject generateToken(String code) {
// [STEP2] 전달 데이터 구성
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("code", code);
formData.add("client_id", CLIENT_ID);
formData.add("grant_type", GRANT_TYPE);
formData.add("client_secret", CLIENT_SECRET);
formData.add("redirect_uri", "http://localhost:8000/api/v1/oauth2/kakao");
// [STEP2] WebClient 이용하여 토큰 발급
return WebClient.builder()
.baseUrl("https://kauth.kakao.com/oauth/token")
.build()
.post()
.header("Content-type", "application/x-www-form-urlencoded;charset=utf-8")
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(JSONObject.class)
.block();
}
}
4.3. ‘접근 코드’를 통해서 사용자 정보를 조회합니다
💡 이전에 반환받은 접근 코드(Access Code)를 통해서 사용자 정보를 조회합니다.
package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.common.codes.SuccessCode;
import com.adjh.multiflexapi.common.codes.constans.MainConstants;
import com.adjh.multiflexapi.common.response.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Spring Boot OAuth 2 Client 구성
*
* @author : jonghoon
* @fileName : OAuth2Controller
* @since : 2023/07/09
*/
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/oauth2")
public class OAuth2Controller {
final int SUCCESS_CODE = SuccessCode.SELECT.getStatus();
final String SUCCESS_MSG = SuccessCode.SELECT.getMessage();
final String MAIN_TAG = MainConstants.MAIN_TAG;
final String CLIENT_ID = "XXXX";
final String CLIENT_SECRET = "XXXX";
final String GRANT_TYPE = "authorization_code";
/**
* 카카오 로그인을 동의 하였을때 처리 이후 redirect 되는 API
*
* @return ResponseEntity<ApiResponse>
* @link
* @since 2023.07.15
*/
@GetMapping("/kakao")
public ResponseEntity<ApiResponse<Object>> kakaoLogin(@RequestParam String code) {
// [STEP1] 인가 코드를 받습니다.
log.debug("[+| Kakao Login AccessToken :: " + code);
// [STEP2] 인가 코드를 기반으로 토큰을 발급받습니다.
JSONObject tokenInfo = generateToken(code);
String accessToken = String.valueOf(tokenInfo.get("access_token"));
int refreshTokenExpiresIn = (int) tokenInfo.get("refresh_token_expires_in");
String refreshToken = String.valueOf(tokenInfo.get("refresh_token"));
String scope = String.valueOf(tokenInfo.get("scope"));
// [STEP3] 접근 코드를 기반으로 사용자 정보를 조회합니다.
JSONObject userInfo = findUserInfo(accessToken);
String kakaoAccount = String.valueOf(userInfo.get("kakao_account"));
System.out.println("kakaoAccount :: " + kakaoAccount);
ApiResponse<Object> ar = ApiResponse
.builder()
.result(kakaoAccount)
.resultCode(SUCCESS_CODE)
.resultMsg(SUCCESS_MSG).build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* 인가 코드를 통해서 토큰을 발급받습니다.
*
* @param {String} code 인가코드
* @return {JSONObject}
*/
private JSONObject generateToken(String code) {
// [STEP2] 전달 데이터 구성
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("code", code);
formData.add("client_id", CLIENT_ID);
formData.add("grant_type", GRANT_TYPE);
formData.add("client_secret", CLIENT_SECRET);
formData.add("redirect_uri", "http://localhost:8000/api/v1/oauth2/kakao");
// [STEP2] WebClient 이용하여 토큰 발급
return WebClient.builder()
.baseUrl("https://kauth.kakao.com/oauth/token")
.build()
.post()
.header("Content-type", "application/x-www-form-urlencoded;charset=utf-8")
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(JSONObject.class)
.block();
}
/**
* 사용자 정보를 조회합니다.
*
* @param accessToken
* @return
*/
private JSONObject findUserInfo(String accessToken) {
// 전달 받은 토큰을 기반으로 사용자 정보를 조회합니다.
return WebClient.builder()
.baseUrl("https://kapi.kakao.com/v2/user/me")
.build()
.post()
.header("Content-type", "application/x-www-form-urlencoded;charset=utf-8")
.header("Authorization", "Bearer " + accessToken)
.retrieve()
.bodyToMono(JSONObject.class)
.block();
}
}
4.4. 결과값 확인
오늘도 감사합니다. 😀
반응형
'Java > Spring Boot' 카테고리의 다른 글
[Java] Spring Boot Webflux 이해하기 -2 : 활용하기 (2) | 2023.08.13 |
---|---|
[Java] Spring Boot Webflux 이해하기 -1 : 흐름 및 주요 특징 이해 (3) | 2023.08.09 |
[Java] Spring Boot OAuth 2 Client 이해하기 -1 : 정의, 흐름, 인증방식 종류 (0) | 2023.07.09 |
[Java] Spring Cloud 이해하기 -1 : 주요 특징으로 이해하기 (0) | 2023.06.19 |
[Java] 스케줄링 & Spring Boot Quartz 이해하고 적용하기 -1 : 설정 및 간단예시 (1) | 2023.04.22 |