Spring Boot Nuxt3 카카오 로그인 및 JWT 토큰 발급

2024. 7. 22. 23:58·프로그래밍 일기/Java & Spring
반응형

 

Spring Boot와 Nuxt3를 이용한 카카오 sso 로그인 개발 및 JWT토큰 발급 방법

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

카카오 개발자 설정은 넘어가도록 하겠습니다.

버전 정보

Spring Boot 2.7.3
Java 11
Nuxt3

# nuxt

<template>
  <div class="container">
    <div class="login-div">
      <div class="login-div-header">로그인</div>
      <a :href="kakaoAuthUrl">
        <img class="login-div-button" src="/images/common/kakao_login_img.png">
      </a>
    </div>

  </div>
</template>

<script setup>
const { baseURL } = useRuntimeConfig().public
const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=efb7a9d2600fa1ee3a40f47c92fee75d&redirect_uri=${baseURL}/api/kakao/callback&response_type=code`
</script>

위와 같이 프론트 화면을 만든 후

# build.gradle

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

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
# KakaoLoginController

package com.reroll.rerollback.api.auth.kakao;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.beans.factory.annotation.Value;

import java.util.Map;

@RestController
public class KakaoLoginController {

    @Value("${front.url}")
    private String frontUrl;

    private final KakaoLoginService kakaoLoginService;

    public KakaoLoginController(KakaoLoginService kakaoLoginService) {
        this.kakaoLoginService = kakaoLoginService;
    }

    @GetMapping("/api/kakao/callback")
    public RedirectView loadChampionList(@RequestParam Map params) throws Exception {
        Map result = kakaoLoginService.kakaoLogin(params);
        String code = (String) result.get("code");
        String accessToken = (String) result.get("accessToken");
        String refreshToken = (String) result.get("refreshToken");
        String adminToken = (String) result.get("adminToken");
        String redirectUrl = "";

        if ("200".equals(code)) {
            redirectUrl = frontUrl + "/login/complete?accessToken=" + accessToken + "&refreshToken=" + refreshToken;
            if (!"".equals(adminToken)) {
                redirectUrl += "&adminToken=" + adminToken;
            }
        } else {
            redirectUrl = frontUrl + "/login";
        }

        return new RedirectView(redirectUrl);
    }

}

위와같이 작성 후 Service 부분에서 WebClient를 활용하여 토큰을 발급받습니다.

# KakaoLoginService

package com.reroll.rerollback.api.auth.kakao;

import com.reroll.rerollback.api.user.user.UserMapper;
import com.reroll.rerollback.util.CommonUtil;
import com.reroll.rerollback.util.JwtUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.HashMap;
import java.util.Map;

@Service
public class KakaoLoginService {

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

    @Value("${kakao.auth.url.host}")
    private String authUrl;

    @Value("${kakao.user.url.host}")
    private String userUrl;

    private final UserMapper userMapper;
    private final JwtUtil jwtUtil;
    private final CommonUtil commonUtil;

    public KakaoLoginService(UserMapper userMapper, JwtUtil jwtUtil, CommonUtil commonUtil) {
        this.userMapper = userMapper;
        this.jwtUtil = jwtUtil;
        this.commonUtil = commonUtil;
    }

    public Map kakaoLogin(Map<String, String> params) throws Exception {
        Map result = new HashMap();
        String code = params.get("code");

        Map<String, Object> authResponse = WebClient.create(authUrl).post()
                .uri(uriBuilder -> uriBuilder
                        .path("/oauth/token")
                        .queryParam("grant_type", "authorization_code")
                        .queryParam("client_id", clientId)
                        .queryParam("code", code)
                        .build())
                .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
                .retrieve()
                .bodyToMono(Map.class)
                .block();

        String accessToken = (String) authResponse.get("access_token");
        Map<String, Object> userResponse = WebClient.create(userUrl)
                .get()
                .uri(uriBuilder -> uriBuilder
                        .path("/v2/user/me")
                        .build())
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
                .retrieve()
                .bodyToMono(Map.class)
                .block();

        Map<String, String> kakaoUserInfo = (Map<String, String>) userResponse.get("kakao_account");

        String email = kakaoUserInfo.get("email");
        String userType = "user";
        String nickname = "";
        Map<String, Object> userInfo = userMapper.loadUserInfo(email);

        if (userInfo != null) {
            userType = (String) userInfo.get("userType");
            nickname = (String) userInfo.get("nickname");
            userMapper.modifyLastLoginDate(email);
        } else {
            Map<String, Object> userMap = new HashMap<>();
            nickname = commonUtil.makeNickname();
            userMap.put("email", email);
            userMap.put("loginType", "kakao");
            userMap.put("nickname", nickname);
            userMapper.addUserInfo(userMap);
        }

        String newAccessToken = jwtUtil.generateToken(email, userType, nickname, 10800000); // 3시간
        String newRefreshToken = jwtUtil.generateToken(email, userType, nickname,  36000000); // 10시간

        result.put("code", "200");
        result.put("accessToken", newAccessToken);
        result.put("refreshToken", newRefreshToken);

        return result;
    }
}

이후 nuxt에서 전달받은 토큰을 쿠키로 저장한다.

<template>
  <div class="container">
    <div class="login-div">
      <div class="login-div-header">로그인 완료</div>
    </div>
  </div>
</template>

<script setup>
const { $auth } = useNuxtApp();
const config = useRuntimeConfig();
const route = useRoute()
const accessToken  = ref('')
const refreshToken  = ref('')

// 페이지 로드 시 쿼리 파라미터 읽기
onMounted(() => {
  accessToken.value = route.query.accessToken || ''
  refreshToken.value = route.query.refreshToken || ''

  // 쿠키 설정
  document.cookie = `reroll_AT=${accessToken.value}; SameSite=None; Secure; Max-Age=10800; Path=/`;
  document.cookie = `reroll_RT=${refreshToken.value}; SameSite=None; Secure; Max-Age=36000; Path=/`;

})
</script>

 

+ 심화작업

전달받은 토큰을 통해 API를 제한하거나 페이지 이동을 제한할 수 있다.

1. API 통신 제어

package com.reroll.rerollback.config.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    private final TokenInterceptor tokenInterceptor;

    public InterceptorConfig(TokenInterceptor tokenInterceptor) {
        this.tokenInterceptor = tokenInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/api/check/**");
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/check/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

이와 같이 작성하면 /api/check url로 요청 받은 값은 TokenInterceptor 로직을 통해 토큰을 검사한다.

package com.reroll.rerollback.config.interceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.reroll.rerollback.util.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Map;

@Component
public class TokenInterceptor implements HandlerInterceptor {

    private JwtUtil jwtUtil;

    public TokenInterceptor(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("accessToken".equals(cookie.getName())) { // adminToken 쿠키 값 확인
                    String token = cookie.getValue();
					// 원하는 로직 개발
                }
            }

            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Admin access only");
            return false;
        } else {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Admin access only");
            return false;
        }
    }
}


2. nuxt 페이지 이동 시 체크
nuxt는 라우터 이동 시 미들웨어를 통해 함수를 실행할 수 있다.

// middleware/auth.global.js
export default defineNuxtRouteMiddleware(async (to, from) => {
    const config = useRuntimeConfig();
    const rerollAT = useCookie('reroll_AT');
    const rerollRT = useCookie('reroll_RT');

    if (process.client) {
        if (!rerollAT?.value) {
            if (rerollRT?.value) {
                const response = await $fetch('/api/token/refresh', {
                    baseURL: config.public.baseURL,
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });

                if (response.code === '200') {
                    document.cookie = `reroll_AT=${response.accessToken}; SameSite=None; Secure; Max-Age=10800; Path=/`;
                    document.cookie = `reroll_RT=${response.refreshToken}; SameSite=None; Secure; Max-Age=36000; Path=/`;
                } else {
                    // 데이터 초기화 로직
                }
            } else {
                // 데이터 초기화 로직
            }
        }

	// 특정 페이지 제한
        if (to.path.startsWith('/chcek')) {
            try {
                if (!rerollAT?.value) {
                    return navigateTo('/login');
                }
            } catch (error) {
                return navigateTo('/login');
            }
        }
    }
});

access 토큰이 만료된 경우 refresh 토큰이 존재한다면 새로운 토큰을 받아오는 로직을 만든다.
또한 토큰값 존재 여부를 통해 특정 페이지 접속을 제한할 수 있다.

반응형
저작자표시 (새창열림)

'프로그래밍 일기 > Java & Spring' 카테고리의 다른 글

JWT란?  (0) 2024.07.09
자바 스프링 메일 안에 이미지 추가하는 방법  (0) 2023.07.18
[에러해결] javax.mail.MessagingException: Could not connect to SMTP host: smtp.worksmobile.com, port: 465, response: -1 (isSSL true 설정)  (0) 2023.07.18
[Spring Boot] 파일 다운로드 로직 구현 feat.Nuxt3  (0) 2023.07.12
[Spring Boot] 단일, 다중 파일 업로드 로직 구현 feat.Nuxt3  (0) 2023.07.12
'프로그래밍 일기/Java & Spring' 카테고리의 다른 글
  • JWT란?
  • 자바 스프링 메일 안에 이미지 추가하는 방법
  • [에러해결] javax.mail.MessagingException: Could not connect to SMTP host: smtp.worksmobile.com, port: 465, response: -1 (isSSL true 설정)
  • [Spring Boot] 파일 다운로드 로직 구현 feat.Nuxt3
MakeMe
MakeMe
제가 포스팅한 글 중 잘못된 부분이 있으면 알려주세요!
  • MakeMe
    Developer blog
    MakeMe
    • 모든 글 (71)
      • 프로그래밍 일기 (57)
        • Java & Spring (21)
        • Python & Flask (3)
        • Linux (12)
        • Front-End (10)
        • DB & SQL (6)
        • Git (3)
        • IDE (2)
      • 자격증 (7)
        • 정보처리기능사 (2)
        • SQLD (1)
        • SW개발_L5 (1)
        • AWS (3)
      • 독립일기 (7)
        • 중소기업청년대출 (7)
  • 인기 글

  • 태그

    중기청서류
    springboot
    고용보험내역서
    인텔리제이
    java
    중기청필수서류
    자동배포설정
    IntelliJ
    flask 세팅
    nuxt3
    MYSQL
    flask
    nuxt
    AWS
    자바
    psql
    중기청필요서류
    젠킨스 자동 배포
    Spring
    DBeaver
    스프링
    넉스트
    스프링부트
    젠킨스 우분투 설치
    중기청후기
    Vue
    자바환경변수
    건강보험자격득실확인서
    DB
    중소기업청년대출
  • hELLO· Designed By정상우.v4.10.1
MakeMe
Spring Boot Nuxt3 카카오 로그인 및 JWT 토큰 발급
상단으로

티스토리툴바