BackEnd/Spring Boot

[Spring] REST API ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ

ddonghyeo 2024. 5. 20. 14:56

์นด์นด์˜ค ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑ
https://developers.kakao.com/docs/latest/ko/kakaologin/common
๊ตฌํ˜„ ์ฝ”๋“œ
https://github.com/DDonghyeo/kakao-login
์ฐธ๊ณ 
์ด ๊ธ€์€ ์ ˆ์ฐจ์— ๋”ฐ๋ผ ์ฝ”๋“œ๋ฅผ ์™„์„ฑํ•ด ๋‚˜๊ฐ€๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ์ „์ฒด ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”.
ํ”„๋ก ํŠธ(ํด๋ผ์ด์–ธํŠธ) ๋ถ€๋ถ„์€ thymeleaf๋กœ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
Spring Boot 3.x.x ๋ฒ„์ „ ๊ธฐ์ค€์ž…๋‹ˆ๋‹ค.

 


๋จผ์ € , ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๋ฐฉ์‹์— ๋Œ€ํ•ด ์ดํ•ดํ•˜๊ณ  ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค.

https://developers.kakao.com/docs/latest/ko/kakaologin/common

์•„๋ž˜์ฒ˜๋Ÿผ ์ •๋ฆฌํ•˜๊ณ  ์ง„ํ–‰ํ•˜๊ฒ ๋‹ค.

  1. ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์š”์ฒญ
    • ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ์š”์ฒญํ•œ๋‹ค. ๋ฏธ๋ฆฌ ์„ค์ •ํ•ด๋‘” client_id (REST API KEY) + redirect URI ๊ฐ€ ์„ค์ •๋œ ๋งํฌ๋กœ ๋“ค์–ด๊ฐ„๋‹ค.
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค ๋กœ๊ทธ์ธ
    • ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์ฐฝ์œผ๋กœ redirect ๋˜๋ฉฐ ์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธํ•œ๋‹ค.
  3. ์•ฑ์— ๋“ฑ๋ก๋œ Redirect URI์™€ ๊ฐ™์ด code ์ „๋‹ฌ ๋ฐ›๊ธฐ
    • ๋กœ๊ทธ์ธ์ด ์™„๋ฃŒ๋˜๋ฉด ๋ฏธ๋ฆฌ ์„ค์ •ํ•ด๋‘” redirect uri๋กœ ๋Œ์•„์˜ค๋ฉฐ, ํŒŒ๋ผ๋ฏธํ„ฐ code๋กœ ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ๋ฐ›๋Š”๋‹ค.
    • ์ธ๊ฐ€์ฝ”๋“œ๋Š” ์นด์นด์˜ค๊ฐ€ ์ „๋‹ฌํ•ด์ฃผ๋Š” ์ฝ”๋“œ์ด๋‹ค.
  4. code๋ฅผ ํ†ตํ•ด ํ† ํฐ ๋ฐœ๊ธ‰ ์š”์ฒญ
    • ๋ฐ›์€ ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ์นด์นด์˜ค์— ๋ณด๋‚ด๋ฉด access token, refresh token์„ ๋ฐ›๋Š”๋‹ค.
  5. ๋ฐ›์€ ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
    • ๋ฐ›์€ access token์„ ์นด์นด์˜ค์—๊ฒŒ ๋ณด๋‚ด์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์š”์ฒญํ•œ๋‹ค.
  6. ํšŒ์›๊ฐ€์ž…, ๋˜๋Š” ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
    • ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋กœ ์„œ๋ฒ„ ๋‚ด์—์„œ ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.



0.  Settings

 

0-1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ฑ

๋จผ์ €, ์นด์นด์˜ค์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

 

https://developers.kakao.com/

 

์นด์นด์˜ค Developer์—์„œ ๋กœ๊ทธ์ธ์„ ํ•˜๊ณ  ๋‚ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋“ค์–ด๊ฐ€์ž.

 

 

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ถ”๊ฐ€ํ•˜๊ธฐ๋ฅผ ๋ˆ„๋ฅธ๋‹ค.

 

 

 

์›ํ•˜๋Š” ์ด๋ฆ„์„ ์ž‘์„ฑํ•˜๊ณ  ์ €์žฅ์„ ๋ˆ„๋ฅธ๋‹ค.

 

 

 

์ƒˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋งŒ๋“ค์–ด์กŒ๋‹ค.

 

 

 

 


์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ˆŒ๋Ÿฌ์„œ ๋“ค์–ด๊ฐ”์„ ๋•Œ, ์•ฑ ํ‚ค ์นธ์—์„œ ํ‚ค๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

์šฐ๋ฆฌ๋Š” REST API๋ฅผ ์ด์šฉํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— REST API ํ‚ค๋ฅผ ๋ณต์‚ฌํ•ด๋‘์ž.





0-2. ๋™์˜ ํ•ญ๋ชฉ ์„ค์ •

 


๋™์˜ํ•ญ๋ชฉ์— ๋“ค์–ด๊ฐ€ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๊ณผ์ •์—์„œ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋ฐ›์„ ์ •๋ณด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์ผ๋ถ€ ํ•ญ๋ชฉ์€ ํ•„์ˆ˜ ๋™์˜๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ผ๋ถ€๋Š” ์นด์นด์˜ค ๋‚ด์—์„œ ๊ฒ€์ˆ˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

๋‹ค๋ฅธ ํ•ญ๋ชฉ ๋˜ํ•œ ์„ค์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ๋น„์ฆˆ ์•ฑ์œผ๋กœ ์ „ํ™˜ํ•˜๋ฉด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

์ผ๋‹จ์€ ๋‹‰๋„ค์ž„๊ณผ ํ”„๋กœํ•„ ์‚ฌ์ง„์„ ํ•„์ˆ˜ ๋™์˜๋กœ ์„ค์ •ํ•˜๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ๋‹ค.

 

โ€ผ๏ธ Email์„ ๋ฐ›์„ ์ˆ˜ ์—†์œผ๋ฉด, ์„œ๋ฒ„ ๋‚ด์—์„œ ์‚ฌ์šฉ์ž ์‹๋ณ„์„ ์–ด๋–ป๊ฒŒ ํ•˜๋‚˜์š”?
๋‹‰๋„ค์ž„์€ uniqueํ•œ ๊ฐ’์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ํšŒ์› ์‹๋ณ„์šฉ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์ง€๋งŒ,
์นด์นด์˜ค ๋‚ด์— auth_id๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 



0-3. RedirectURI ๋“ฑ๋ก

 

์นด์นด์˜ค๋กœ๊ทธ์ธ ํƒญ์— ๋“ค์–ด๊ฐ€์„œ ํ™œ์„ฑํ™”๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด, RedirectURI๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค ๋กœ๊ทธ์ธ์„ ์„ฑ๊ณตํ•˜๋ฉด, ์นด์นด์˜ค ์ธก์—์„œ Client๋ฅผ Redirect ํ•ด์ค„ URI๋ฅผ ์„ค์ •ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

 

ํ•œ ๋งˆ๋””๋กœ, ์นด์นด์˜ค ์ธก์—์„œ "์šฐ๋ฆฌ ๋กœ๊ทธ์ธ ์™„๋ฃŒ๋˜๋ฉด ์–ด๋””๋กœ ๋ณด๋‚ด์ค„๊นŒ?" ๋ฅผ ์ •ํ•ด๋‘๋Š” ๊ฒƒ์ด๋‹ค.

 

*์—ฌ๊ธฐ์„œ ๋“ฑ๋กํ•œ RedirectURI๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์‚ฌ์šฉํ•  ์„œ๋ฒ„์˜ ๋„๋ฉ”์ธ URI์ค‘ ํ•˜๋‚˜๋ฅผ ๋“ฑ๋กํ•˜์ž.
*์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•  RedirectURI๋Š” ์œ„ ๊ณผ์ • ์ค‘ 3๋ฒˆ -> 4๋ฒˆ ๊ณผ์ •์—์„œ ์‚ฌ์šฉํ•˜๋Š” URI์— ํ•ด๋‹นํ•œ๋‹ค.

 

 

 

 

 


์ž„์‹œ ํ…Œ์ŠคํŠธ ์šฉ์œผ๋กœ ์œ„์™€ ๊ฐ™์ด ๋“ฑ๋กํ–ˆ๋‹ค.

 

 




0-4. ์˜์กด์„ฑ ์ถ”๊ฐ€

  • build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	//Webflux
	implementation 'org.springframework.boot:spring-boot-starter-webflux'

	//thymeleaf
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
}

 

HTTP ์š”์ฒญ์„ Webflux Webclient๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

๋งŒ์•ฝ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์„ ์˜ˆ์ •์ด๋ผ๋ฉด, thymeleaf๋Š” ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

 

1. ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์š”์ฒญ

์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค ๋กœ๊ทธ์ธ์„ ํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.

 

์ด ๋ถ€๋ถ„์€ ํ”„๋ก ํŠธ์˜ ๊ฐœ์ž…์ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค.

 

์‚ฌ์šฉ์ž๊ฐ€ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๋งํฌ๋ฅผ ๋“ค์–ด๊ฐ€ ๋กœ๊ทธ์ธ ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

 

1-1. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๋งŒ๋“ค๊ธฐ

https://developers.kakao.com/docs/latest/ko/kakaologin/design-guide

์นด์นด์˜ค์—์„œ ๋ฒ„ํŠผ ๋””์ž์ธ์— ๋Œ€ํ•œ ๊ฐ€์ด๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

png๋ฅผ ์ง์ ‘ ๋‹ค์šด๋ฐ›๊ณ , thymeleaf๋ฅผ ์จ์„œ ๊ตฌํ˜„ํ•ด ๋ณด์•˜๋‹ค.

 

  • build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'



  • application.yml -> ์ดˆ๊ธฐ์— ์„ธํŒ…ํ–ˆ๋˜ REST API ํ‚ค, Redirect URI๋ฅผ ๋“ฑ๋ก
kakao:
  client_id: {REST API KEY}
  redirect_uri: http://localhost:8080/callback



1-2. Page Controller

  • Page Controller

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค„ ์ปจํŠธ๋กค๋Ÿฌ

@Controller
@RequestMapping("/login")
public class KakaoLoginPageController {

    @Value("${kakao.client_id}")
    private String client_id;

    @Value("${kakao.redirect_uri}")
    private String redirect_uri;

    @GetMapping("/page")
    public String loginPage(Model model) {
        String location = "https://kauth.kakao.com/oauth/authorize?response_type=code&client_id="+client_id+"&redirect_uri="+redirect_uri;
        model.addAttribute("location", location);

        return "login";
    }
}

 

1-3. Login Page HTML

  • resources/templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>KakaoLogin</title>
</head>
<body>
<div class="container" style="display: flex; justify-content: center; align-content: center; align-items: center; flex-direction: column; margin: 200px auto; ">
    <h1>์นด์นด์˜ค ๋กœ๊ทธ์ธ</h1>
    <a th:href="${location}">
        <img src="/kakao_login_medium_narrow.png" >
    </a>
</div>
</body>
</html>


/resources/static ๊ฒฝ๋กœ์— pngํŒŒ์ผ์„ ์ถ”๊ฐ€ 

 

์ด์ œ, http://localhost:8080/login/page ์— ์ ‘์†ํ•˜๋ฉด

๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ ํ”„๋ก ํŠธ ์™„์„ฑ ! !






 

 

 

2. ์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค ๋กœ๊ทธ์ธ

์นด์นด์˜ค ๋กœ๊ทธ์ธ์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ REST APIํ‚ค์™€ redirect uri๊ฐ€ ์ฒจ๋ถ€๋œ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๋งํฌ๋กœ ๋“ค์–ด๊ฐ€๋ฉฐ ์‹œ์ž‘๋œ๋‹ค.

https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}

 

 

๋งŒ์•ฝ thymeleaf๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ๊ทธ๋ƒฅ ๋งํฌ๋ฅผ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ์ ‘์†ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

โ€ผ๏ธ Tip
์—ฌ๋Ÿฌ๋ฒˆ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์บ์‹œ๋•Œ๋ฌธ์— ์ž๋™ ๋กœ๊ทธ์ธ ๋˜๋‹ˆ ์บ์‹œ ์‚ญ์ œ ํ›„ ํ…Œ์ŠคํŠธํ•˜๊ฑฐ๋‚˜ ๋ธŒ๋ผ์šฐ์ € ์‹œํฌ๋ฆฟ ๋ชจ๋“œ๋กœ ํ™œ์šฉ

 

 

ํ•ด๋‹น ๋งํฌ๋กœ ๋“ค์–ด๊ฐ€๋ณด๋ฉด, ์ดˆ๊ธฐ์— ์„ค์ •ํ–ˆ๋˜ ๋™์˜ํ•ญ๋ชฉ๊ณผ ํ•จ๊ป˜ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๋“ฑ๋กํ•ด๋‘” Redirect URI์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ code์™€ ํ•จ๊ป˜ Redirect๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.










3. ์•ฑ์— ๋“ฑ๋ก๋œ Redirect URI์™€ ๊ฐ™์ด code ์ „๋‹ฌ ๋ฐ›๊ธฐ

๊ทธ๋Ÿผ ์ด์ œ, Redirect๋œ URI์— ์ „๋‹ฌ๋œ code๋ฅผ ๊ฐ€์ ธ์™€๋ณด์ž.

 

3-1. KakaoLoginController

  • KakaoLoginController (์ถ”ํ›„์— ๋” ์ˆ˜์ •๋จ)
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("")
public class KakaoLoginController {

    @GetMapping("/callback")
    public ResponseEntity<?> callback(@RequestParam("code") String code) {

        return new ResponseEntity<>(HttpStatus.OK);
    }
}

 

์œ„์™€ ๊ฐ™์ด, ๋“ฑ๋กํ•ด๋‘” URI์— GetMapping์„ ํ•ด๋‘๋ฉด ํŒŒ๋ผ๋ฏธํ„ฐ code์™€ ํ•จ๊ป˜ ๋ฐ›์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.










4. code๋ฅผ ํ†ตํ•ด ํ† ํฐ ๋ฐœ๊ธ‰ ์š”์ฒญ

์ด์ œ ์นด์นด์˜ค๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ code๋ฅผ ์นด์นด์˜ค์— ํ† ํฐ ๋ฐœ๊ธ‰ ์š”์ฒญ์„ ํ•˜๋ฉด, ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ์žˆ๋Š” ํ† ํฐ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

 

https://kauth.kakao.com/oauth/token URL๋กœ POST ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ํ† ํฐ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.   

 

ํ† ํฐ๊นŒ์ง€ ๋ฐ›์•„์•ผ ๋กœ๊ทธ์ธ์ด ์นด์นด์˜ค ๋กœ๊ทธ์ธ์ด ์™„๋ฃŒ๋œ๋‹ค!

 

 

4-1. KakaoService

  • KakaoTokenResponseDto.java
@Getter
@NoArgsConstructor //์—ญ์ง๋ ฌํ™”๋ฅผ ์œ„ํ•œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž
@JsonIgnoreProperties(ignoreUnknown = true)
public class KakaoTokenResponseDto {

    @JsonProperty("token_type")
    public String tokenType;
    @JsonProperty("access_token")
    public String accessToken;
    @JsonProperty("id_token")
    public String idToken;
    @JsonProperty("expires_in")
    public Integer expiresIn;
    @JsonProperty("refresh_token")
    public String refreshToken;
    @JsonProperty("refresh_token_expires_in")
    public Integer refreshTokenExpiresIn;
    @JsonProperty("scope")
    public String scope;
}

 

์นด์นด์˜ค๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ Response DTO๋ฅผ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด ๋‘”๋‹ค.

 

์—ญ์ง๋ ฌํ™”๋ฅผ ์œ„ํ•ด @JsonProperty๋ฅผ ์ด์šฉํ•˜์—ฌ ๋งคํ•‘ํ•ด์ฃผ๊ณ , default ์ƒ์„ฑ์ž๋ฅผ ์„ ์–ธํ•ด๋‘”๋‹ค.

 

์ž์„ธํ•œ API๋Š” https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token ์—์„œ ํ™•์ธ

 

 

  • kakaoService.java (์ถ”ํ›„์— ๋” ์ถ”๊ฐ€๋จ)
@Slf4j
@RequiredArgsConstructor
@Service
public class KakaoService {

    private String clientId;
    private final String KAUTH_TOKEN_URL_HOST;
    private final String KAUTH_USER_URL_HOST;

    @Autowired
    public KakaoService(@Value("${kakao.client_id}") String clientId) {
        this.clientId = clientId;
        KAUTH_TOKEN_URL_HOST ="https://kauth.kakao.com";
        KAUTH_USER_URL_HOST = "https://kapi.kakao.com";
    }

    public String getAccessTokenFromKakao(String code) {
        
        KakaoTokenResponseDto kakaoTokenResponseDto = WebClient.create(KAUTH_TOKEN_URL_HOST).post()
                .uri(uriBuilder -> uriBuilder
                        .scheme("https")
                        .path("/oauth/token")
                        .queryParam("grant_type", "authorization_code")
                        .queryParam("client_id", clientId)
                        .queryParam("code", code)
                        .build(true))
                .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                .retrieve()
                //TODO : Custom Exception
                .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter")))
                .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error")))
                .bodyToMono(KakaoTokenResponseDto.class)
                .block();


        log.info(" [Kakao Service] Access Token ------> {}", kakaoTokenResponseDto.getAccessToken());
        log.info(" [Kakao Service] Refresh Token ------> {}", kakaoTokenResponseDto.getRefreshToken());
        //์ œ๊ณต ์กฐ๊ฑด: OpenID Connect๊ฐ€ ํ™œ์„ฑํ™” ๋œ ์•ฑ์˜ ํ† ํฐ ๋ฐœ๊ธ‰ ์š”์ฒญ์ธ ๊ฒฝ์šฐ ๋˜๋Š” scope์— openid๋ฅผ ํฌํ•จํ•œ ์ถ”๊ฐ€ ํ•ญ๋ชฉ ๋™์˜ ๋ฐ›๊ธฐ ์š”์ฒญ์„ ๊ฑฐ์นœ ํ† ํฐ ๋ฐœ๊ธ‰ ์š”์ฒญ์ธ ๊ฒฝ์šฐ
        log.info(" [Kakao Service] Id Token ------> {}", kakaoTokenResponseDto.getIdToken());
        log.info(" [Kakao Service] Scope ------> {}", kakaoTokenResponseDto.getScope());

        return kakaoTokenResponseDto.getAccessToken();
    }
}


Webclient๋กœ HTTP ์š”์ฒญ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

๊ฐ ์„œ๋น„์Šค๋งˆ๋‹ค Custom Exception์ด ์žˆ๋‹ค๋ฉด onStatus ๋ฉ”์„œ๋“œ์— ์ง€์ •ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

 

HTTP ์š”์ฒญ์„ ๋ฐ›์•„์˜ค๋ฉด .retrieve() ๋ฉ”์„œ๋“œ ๋ถ€ํ„ฐ, Request Body ๋‚ด์šฉ์ด

๋ฏธ๋ฆฌ ์ง€์ •ํ•ด๋‘” KakaoTokenResponseDto์— json์ด ์ง๋ ฌํ™”๋˜์–ด ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

 

https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=${client_id}&code=${code}

 

 

์œ„ url์— POST ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ๋ฐ›์€ Response์˜ Body์— ์•„๋ž˜ ๋‚ด์šฉ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

  • ์š”์ฒญ ์˜ˆ์‹œ
    curl -v -X POST "https://kauth.kakao.com/oauth/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=authorization_code" \
    -d "client_id=${REST_API_KEY}" \
    --data-urlencode "redirect_uri=${REDIRECT_URI}" \
    -d "code=${AUTHORIZE_CODE}"

 

  • ์‘๋‹ต ์˜ˆ์‹œ
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
    "token_type":"bearer",
    "access_token":"${ACCESS_TOKEN}",
    "expires_in":43199,
    "refresh_token":"${REFRESH_TOKEN}",
    "refresh_token_expires_in":5184000,
    "scope":"account_email profile"
}


์—ฌ๊ธฐ์„œ expire์€ ๊ธฐํ•œ,

scope๋Š” ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ •๋ณด๋“ค์˜ ํ•ญ๋ชฉ์ด๋‹ค.

 

์ด๋ฅผ ํ†ตํ•ด ์นด์นด์˜ค๋กœ๋ถ€ํ„ฐ ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์•˜๋‹ค.







๊ตฌํ˜„ํ•œ Service๋ฅผ Controller์—์„œ ์—ฐ๋™ํ•˜์ž.

4-2. KakaoLoginController

  • KakaoLoginController.java (์ถ”ํ›„์— ๋” ์ˆ˜์ •๋จ)
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("")
public class KakaoLoginController {

    private final KakaoService kakaoService;

    @GetMapping("/callback")
    public ResponseEntity<?> callback(@RequestParam("code") String code) throws IOException {
        String accessToken = kakaoService.getAccessTokenFromKakao(code);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

 

์ด์ œ code๋ฅผ ์ด์šฉํ•ด์„œ accessToken์„ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋๋‹ค.



โ€ผ๏ธ ์—ฌ๊ธฐ๊นŒ์ง€ ํ† ํฐ์„ ๋ฐ›์•˜์œผ๋ฏ€๋กœ, ์นด์นด์˜ค ๋กœ๊ทธ์ธ์ด ์™„๋ฃŒ๋œ ์ƒํ™ฉ์ด๋‹ค.
์—ฌ๊ธฐ์„œ ๋ฉˆ์ถฐ์„œ ๋ฐ›์€ ํ† ํฐ์„ ํ†ตํ•ด ์ธ์ฆ, ์ธ๊ฐ€๋ฅผ ๊ตฌํ˜„ํ•ด๋„ ๋˜์ง€๋งŒ,
์ถ”๊ฐ€๋กœ ํ† ํฐ์„ ์ด์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ›์€ ํ›„ ์„œ๋ฒ„์— ํšŒ์›๊ฐ€์ž…์„ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ•œ๋‹ค.











5. ๋ฐ›์€ ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

์ด์ œ, ์นด์นด์˜ค์—์„œ ๋ฐ›์€ access token์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค์ž.



5-1. KakaoService (์ถ”๊ฐ€)

  • KakaoUserInfoResponseDto.java
@Getter
@NoArgsConstructor //์—ญ์ง๋ ฌํ™”๋ฅผ ์œ„ํ•œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž
@JsonIgnoreProperties(ignoreUnknown = true)
public class KakaoUserInfoResponseDto {

    //ํšŒ์› ๋ฒˆํ˜ธ
    @JsonProperty("id")
    public Long id;

    //์ž๋™ ์—ฐ๊ฒฐ ์„ค์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•œ ๊ฒฝ์šฐ๋งŒ ์กด์žฌ.
    //true : ์—ฐ๊ฒฐ ์ƒํƒœ, false : ์—ฐ๊ฒฐ ๋Œ€๊ธฐ ์ƒํƒœ
    @JsonProperty("has_signed_up")
    public Boolean hasSignedUp;

    //์„œ๋น„์Šค์— ์—ฐ๊ฒฐ ์™„๋ฃŒ๋œ ์‹œ๊ฐ. UTC
    @JsonProperty("connected_at")
    public Date connectedAt;

    //์นด์นด์˜ค์‹ฑํฌ ๊ฐ„ํŽธ๊ฐ€์ž…์„ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•œ ์‹œ๊ฐ. UTC
    @JsonProperty("synched_at")
    public Date synchedAt;

    //์‚ฌ์šฉ์ž ํ”„๋กœํผํ‹ฐ
    @JsonProperty("properties")
    public HashMap<String, String> properties;

    //์นด์นด์˜ค ๊ณ„์ • ์ •๋ณด
    @JsonProperty("kakao_account")
    public KakaoAccount kakaoAccount;

    //uuid ๋“ฑ ์ถ”๊ฐ€ ์ •๋ณด
    @JsonProperty("for_partner")
    public Partner partner;

    @Getter
    @NoArgsConstructor
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class KakaoAccount {

        //ํ”„๋กœํ•„ ์ •๋ณด ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("profile_needs_agreement")
        public Boolean isProfileAgree;

        //๋‹‰๋„ค์ž„ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("profile_nickname_needs_agreement")
        public Boolean isNickNameAgree;

        //ํ”„๋กœํ•„ ์‚ฌ์ง„ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("profile_image_needs_agreement")
        public Boolean isProfileImageAgree;

        //์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ •๋ณด
        @JsonProperty("profile")
        public Profile profile;

        //์ด๋ฆ„ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("name_needs_agreement")
        public Boolean isNameAgree;

        //์นด์นด์˜ค๊ณ„์ • ์ด๋ฆ„
        @JsonProperty("name")
        public String name;

        //์ด๋ฉ”์ผ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("email_needs_agreement")
        public Boolean isEmailAgree;

        //์ด๋ฉ”์ผ์ด ์œ ํšจ ์—ฌ๋ถ€
        // true : ์œ ํšจํ•œ ์ด๋ฉ”์ผ, false : ์ด๋ฉ”์ผ์ด ๋‹ค๋ฅธ ์นด์นด์˜ค ๊ณ„์ •์— ์‚ฌ์šฉ๋ผ ๋งŒ๋ฃŒ
        @JsonProperty("is_email_valid")
        public Boolean isEmailValid;

        //์ด๋ฉ”์ผ์ด ์ธ์ฆ ์—ฌ๋ถ€
        //true : ์ธ์ฆ๋œ ์ด๋ฉ”์ผ, false : ์ธ์ฆ๋˜์ง€ ์•Š์€ ์ด๋ฉ”์ผ
        @JsonProperty("is_email_verified")
        public Boolean isEmailVerified;

        //์นด์นด์˜ค๊ณ„์ • ๋Œ€ํ‘œ ์ด๋ฉ”์ผ
        @JsonProperty("email")
        public String email;

        //์—ฐ๋ น๋Œ€ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("age_range_needs_agreement")
        public Boolean isAgeAgree;

        //์—ฐ๋ น๋Œ€
        //์ฐธ๊ณ  https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
        @JsonProperty("age_range")
        public String ageRange;

        //์ถœ์ƒ ์—ฐ๋„ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("birthyear_needs_agreement")
        public Boolean isBirthYearAgree;

        //์ถœ์ƒ ์—ฐ๋„ (YYYY ํ˜•์‹)
        @JsonProperty("birthyear")
        public String birthYear;

        //์ƒ์ผ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("birthday_needs_agreement")
        public Boolean isBirthDayAgree;

        //์ƒ์ผ (MMDD ํ˜•์‹)
        @JsonProperty("birthday")
        public String birthDay;

        //์ƒ์ผ ํƒ€์ž…
        // SOLAR(์–‘๋ ฅ) ํ˜น์€ LUNAR(์Œ๋ ฅ)
        @JsonProperty("birthday_type")
        public String birthDayType;

        //์„ฑ๋ณ„ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("gender_needs_agreement")
        public Boolean isGenderAgree;

        //์„ฑ๋ณ„
        @JsonProperty("gender")
        public String gender;

        //์ „ํ™”๋ฒˆํ˜ธ ์ œ๊ณต ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("phone_number_needs_agreement")
        public Boolean isPhoneNumberAgree;

        //์ „ํ™”๋ฒˆํ˜ธ
        //๊ตญ๋‚ด ๋ฒˆํ˜ธ์ธ ๊ฒฝ์šฐ +82 00-0000-0000 ํ˜•์‹
        @JsonProperty("phone_number")
        public String phoneNumber;

        //CI ๋™์˜ ์—ฌ๋ถ€
        @JsonProperty("ci_needs_agreement")
        public Boolean isCIAgree;

        //CI, ์—ฐ๊ณ„ ์ •๋ณด
        @JsonProperty("ci")
        public String ci;

        //CI ๋ฐœ๊ธ‰ ์‹œ๊ฐ, UTC
        @JsonProperty("ci_authenticated_at")
        public Date ciCreatedAt;

        @Getter
        @NoArgsConstructor
        @JsonIgnoreProperties(ignoreUnknown = true)
        public class Profile {

            //๋‹‰๋„ค์ž„
            @JsonProperty("nickname")
            public String nickName;

            //ํ”„๋กœํ•„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ URL
            @JsonProperty("thumbnail_image_url")
            public String thumbnailImageUrl;

            //ํ”„๋กœํ•„ ์‚ฌ์ง„ URL
            @JsonProperty("profile_image_url")
            public String profileImageUrl;

            //ํ”„๋กœํ•„ ์‚ฌ์ง„ URL ๊ธฐ๋ณธ ํ”„๋กœํ•„์ธ์ง€ ์—ฌ๋ถ€
            //true : ๊ธฐ๋ณธ ํ”„๋กœํ•„, false : ์‚ฌ์šฉ์ž ๋“ฑ๋ก
            @JsonProperty("is_default_image")
            public String isDefaultImage;

            //๋‹‰๋„ค์ž„์ด ๊ธฐ๋ณธ ๋‹‰๋„ค์ž„์ธ์ง€ ์—ฌ๋ถ€
            //true : ๊ธฐ๋ณธ ๋‹‰๋„ค์ž„, false : ์‚ฌ์šฉ์ž ๋“ฑ๋ก
            @JsonProperty("is_default_nickname")
            public Boolean isDefaultNickName;

        }
    }

    @Getter
    @NoArgsConstructor
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Partner {
        //๊ณ ์œ  ID
        @JsonProperty("uuid")
        public String uuid;
    }

}

 

์นด์นด์˜ค์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋‚ด์šฉ์„ ๋ชจ๋‘ ๊ธฐ์žฌํ–ˆ๋‹ค.

 

๊ฐ„๋‹จํ•œ ์„ค๋ช…์€ ์ฃผ์„์œผ๋กœ ์ถ”๊ฐ€ํ•ด ๋‘์—ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ, ์‚ฌ์šฉ์ž๊ฐ€ ๋™์˜ํ•œ ํ•ญ๋ชฉ๋งŒ ๋‹ด๊ฒจ์ง€๊ณ , ๋™์˜ํ•˜์ง€ ์•Š์€ ํ•ญ๋ชฉ์€ null๋กœ ๋“ค์–ด์˜ค๊ฒŒ ๋œ๋‹ค.

 

๋™์˜ ํ•ญ๋ชฉ์„ ๊ณ ๋ คํ•˜์—ฌ ์ž์œ ๋กญ๊ฒŒ ์ปค์Šคํ…€ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

 

 

 

  • KakaoService์— ์ถ”๊ฐ€
public KakaoUserInfoResponseDto getUserInfo(String accessToken) {

        KakaoUserInfoResponseDto userInfo = WebClient.create(KAUTH_USER_URL_HOST)
                .get()
                .uri(uriBuilder -> uriBuilder
                        .scheme("https")
                        .path("/v2/user/me")
                        .build(true))
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) // access token ์ธ๊ฐ€
                .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                .retrieve()
                //TODO : Custom Exception
                .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter")))
                .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error")))
                .bodyToMono(KakaoUserInfoResponseDto.class)
                .block();

        log.info("[ Kakao Service ] Auth ID ---> {} ", userInfo.getId());
        log.info("[ Kakao Service ] NickName ---> {} ", userInfo.getKakaoAccount().getProfile().getNickName());
        log.info("[ Kakao Service ] ProfileImageUrl ---> {} ", userInfo.getKakaoAccount().getProfile().getProfileImageUrl());

        return userInfo;
    }


๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Webclient๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

์œ„ ๋กœ์ง์€

https://kapi.kakao.com/v2/user/me

 

์œ„ URL์˜ ํ—ค๋” Authorization์— Bearer token์„ ์ถ”๊ฐ€ํ•ด์„œ GET์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, Response์— ์•„๋ž˜์™€ ๊ฐ™์€ Body๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.



์ž์„ธํ•œ๊ฑด https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info ์—์„œ ํ™•์ธ

 

  • Response Body (์˜ˆ์‹œ)
HTTP/1.1 200 OK
{
    "id":123456789,
    "connected_at": "2022-04-11T01:45:28Z",
    "kakao_account": { 
        // ํ”„๋กœํ•„ ๋˜๋Š” ๋‹‰๋„ค์ž„ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "profile_nickname_needs_agreement": false,
        // ํ”„๋กœํ•„ ๋˜๋Š” ํ”„๋กœํ•„ ์‚ฌ์ง„ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "profile_image_needs_agreement": false,
        "profile": {
            // ํ”„๋กœํ•„ ๋˜๋Š” ๋‹‰๋„ค์ž„ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
            "nickname": "ํ™๊ธธ๋™",
            // ํ”„๋กœํ•„ ๋˜๋Š” ํ”„๋กœํ•„ ์‚ฌ์ง„ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
            "thumbnail_image_url": "http://yyy.kakao.com/.../img_110x110.jpg",
            "profile_image_url": "http://yyy.kakao.com/dn/.../img_640x640.jpg",
            "is_default_image":false,
            "is_default_nickname": false
        },
        // ์ด๋ฆ„ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "name_needs_agreement":false, 
        "name":"ํ™๊ธธ๋™",
        // ์นด์นด์˜ค๊ณ„์ •(์ด๋ฉ”์ผ) ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "email_needs_agreement":false, 
        "is_email_valid": true,   
        "is_email_verified": true,
        "email": "sample@sample.com",
        // ์—ฐ๋ น๋Œ€ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "age_range_needs_agreement":false,
        "age_range":"20~29",
        // ์ถœ์ƒ ์—ฐ๋„ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "birthyear_needs_agreement": false,
        "birthyear": "2002",
        // ์ƒ์ผ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "birthday_needs_agreement":false,
        "birthday":"1130",
        "birthday_type":"SOLAR",
        // ์„ฑ๋ณ„ ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "gender_needs_agreement":false,
        "gender":"female",
        // ์นด์นด์˜ค๊ณ„์ •(์ „ํ™”๋ฒˆํ˜ธ) ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "phone_number_needs_agreement": false,
        "phone_number": "+82 010-1234-5678",   
        // CI(์—ฐ๊ณ„์ •๋ณด) ๋™์˜ํ•ญ๋ชฉ ํ•„์š”
        "ci_needs_agreement": false,
        "ci": "${CI}",
        "ci_authenticated_at": "2019-03-11T11:25:22Z",
    },
    "properties":{
        "${CUSTOM_PROPERTY_KEY}": "${CUSTOM_PROPERTY_VALUE}",
        ...
    },
    "for_partner": {
        "uuid": "${UUID}"
    }
}

 

์œ„ json์— ๋‹ด๊ฒจ์žˆ๋Š” ์ •๋ณด๋Š” ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์–ด๋Š ์ •๋ณด๋ฅผ ๋™์˜ ๋ฐ›์•˜๋Š”์ง€ ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค.

 

๋™์˜ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ null์ด ๋“ค์–ด์˜ค๋‹ˆ ๊ฒ€์‚ฌํ•˜๋Š” ๋กœ์ง์„ ๊ผญ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด, ์•ฑ์— ๋‹‰๋„ค์ž„๋งŒ ๋™์˜ ํ•ญ๋ชฉ์— ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ Response๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

HTTP/1.1 200 OK
{
    "id":123456789,
    "connected_at": "2022-04-11T01:45:28Z",
    "kakao_account": { 
        "profile_nickname_needs_agreement": false,
        "profile": {
            "nickname": "ํ™๊ธธ๋™"
        }
    },  
    "properties":{
        "${CUSTOM_PROPERTY_KEY}": "${CUSTOM_PROPERTY_VALUE}",
        ...
    }
}

 

์œ„ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•ด์„œ ํ•„์š”ํ•œ ์ •๋ณด๋“ค์„ ์ด์šฉํ•˜์—ฌ ์„œ๋ฒ„์— ํšŒ์›๊ฐ€์ž…์„ ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.


 

5-2. KakaoLoginController (์ตœ์ข…)

  • KakaoLoginController (์ตœ์ข…)
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("")
public class KakaoLoginController {

    private final KakaoService kakaoService;

    @GetMapping("/callback")
    public ResponseEntity<?> callback(@RequestParam("code") String code) {
        String accessToken = kakaoService.getAccessTokenFromKakao(code);

        KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken);

        // ์—ฌ๊ธฐ์— ์„œ๋ฒ„ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ(์ธ์ฆ) ๋˜๋Š” ํšŒ์›๊ฐ€์ž… ๋กœ์ง ์ถ”๊ฐ€
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

 

 

์ด์ œ ๋กœ๊ทธ์ธ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋™์˜ํ•œ ์ •๋ณด๋“ค์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

 

์นด์นด์˜ค๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ •๋ณด๋“ค์„ ํ†ตํ•ด์„œ ์„œ๋ฒ„์— ํšŒ์›๊ฐ€์ž… ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

 

์นด์นด์˜ค์—์„œ ๋ฐ›์€ ํ† ํฐ์œผ๋กœ ์ธ๊ฐ€๋ฅผ ํ•ด๋„ ๋˜๊ณ , ์„œ๋ฒ„ ๋‚ด์—์„œ ์‚ฌ์šฉ์ค‘์ธ ์ธ๊ฐ€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๋ฉด ์ƒˆ๋กœ ๋ฐœ๊ธ‰ํ•ด์ค˜๋„ ๋˜๊ฒ ๋‹ค.



์นด์นด์˜ค ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด,

  • ์ถ”๊ฐ€ ํ•ญ๋ชฉ ๋™์˜ ๋ฐ›๊ธฐ
  • ์นด์นด์˜คํ†ก์—์„œ ์ž๋™ ๋กœ๊ทธ์ธํ•˜๊ธฐ
  • ์•ฝ๊ด€ ์„ ํƒํ•ด ๋™์˜ ๋ฐ›๊ธฐ
  • OpenID Connect ID ํ† ํฐ ๋ฐœ๊ธ‰ํ•˜๊ธฐ
  • ๊ธฐ์กด ๋กœ๊ทธ์ธ ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด ๋กœ๊ทธ์ธํ•˜๊ธฐ
  • ์นด์นด์˜ค๊ณ„์ • ๊ฐ€์ž… ํ›„ ๋กœ๊ทธ์ธํ•˜๊ธฐ
  • ๋กœ๊ทธ์ธ ํžŒํŠธ ์ฃผ๊ธฐ
  • ์นด์นด์˜ค๊ณ„์ • ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ

๋“ฑ๋“ฑ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€๋กœ ์ด์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ, ํ•„์š”ํ•œ ์„œ๋น„์Šค๋Š” ์ฐธ๊ณ ํ•ด์„œ ์ด์šฉํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

 

์นด์นด์˜ค REST API Docs
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#before-you-begin