카테고리 없음

카카오 로그인 (OAuth) #2. 카카오 사용자 정보로 회원가입 구현

luminous_dev 2025. 2. 7. 13:54

관심 상품 등록을 했을 때 회원 구분이 필요하기 때문에, 카카오서버에서 받은 사용자 정보를 이용해 회원 가입

 

현재 회원 (User) 테이블

컬럼명 컬럼 타입  중복 허용 설명 
id Long X 테이블 ID(PK)
username String X 회원 ID
password String O 패스워드
email String X 이메일 주소
role String O 역할
1) 사용자 : USER
2) 관리자 : ADMIN 

 

  • 카카오로 부터 받은 사용자 정보
    1. kakaoId
    2. nickname
    3. email
{
  "id": 1632335751,
  "properties": {
    "nickname": "르탄이",
    "profile_image": "http://k.kakaocdn.net/...jpg",
    "thumbnail_image": "http://k.kakaocdn.net/...jpg"
  },
  "kakao_account": {
    "profile_needs_agreement": false,
    "profile": {
      "nickname": "르탄이",
      "thumbnail_image_url": "http://k.kakaocdn.net/...jpg",
      "profile_image_url": "http://k.kakaocdn.net/...jpg"
    },
    "has_email": true,
    "email_needs_agreement": false,
    "is_email_valid": true,
    "is_email_verified": true,
    "email": "letan@sparta.com"
  }
}

 

테이블 설계 옵션

 

  1. 카카오 User 를 위한 테이블 (ex. KakaoUser) 을 하나 더 만든다.
    1. 장점: 결합도가 낮아짐
      1. 성격이 다른 유저 별로 분리 → 차후 각 테이블의 변화에 서로 영향을 주지 않음
      2. 예) 카카오 사용자들만 profile_image 컬럼 추가해서 사용 가능
    2. 단점: 구현 난이도가 올라감
      1. 예) 관심상품 등록 시, 회원별로 다른 테이블을 참조해야 함
        1. 일반 회원: User - Product
        2. 카카오 회원: KakaoUser - Product
    1. 기존 회원 (User) 테이블에 카카오 User 추가
      1. 장점: 구현이 단순해짐
      2. 단점: 결합도가 높아짐
        1. 폼 로그인을 통해 카카오 로그인 사용자의 username, password 를 입력해서 로그인한다면??
          문제가 생길 수 있음 

BUT 회원 (User)테이블에 적용하기로 하였음 

 

변경 후

컬럼명 컬럼 타입  중복 허용 설명  카카오 사용자
id Long X 테이블 ID(PK) 테이블 ID(PK)
username String X 회원 ID nickname
password String O 패스워드 UUID(랜덤 생성)
email String X 이메일 주소 email
role String O 역할
1) 사용자 : USER
2) 관리자 : ADMIN 
"USER"로 고정 
kakaoId String (Nullable) X 카카오 로그인 ID kakaoId

 

패스워드를 UUID로 설정한 이유 

: 폼 로그인을 통해서 로그인이 되지 않도록

 

 

  • 카카오 사용자 정보로 회원가입
    • User 테이블에 'kakaoId' 추가

kakaoService에 필요시 회원가입하는 코드 추가 

 

회원 가입 처리 코드

private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
    // DB 에 중복된 Kakao Id 가 있는지 확인
    Long kakaoId = kakaoUserInfo.getId();
    User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);

    if (kakaoUser == null) {
        // 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
        String kakaoEmail = kakaoUserInfo.getEmail();
        User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
        if (sameEmailUser != null) {
            kakaoUser = sameEmailUser;
            // 기존 회원정보에 카카오 Id 추가
            kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
        } else {
            // 신규 회원가입
            // password: random UUID
            String password = UUID.randomUUID().toString();
            String encodedPassword = passwordEncoder.encode(password);

            // email: kakao email
            String email = kakaoUserInfo.getEmail();

            kakaoUser = new User(kakaoUserInfo.getNickname(), encodedPassword, email, UserRoleEnum.USER, kakaoId);
        }

        userRepository.save(kakaoUser);
    }
    return kakaoUser;
}

 

kakaoService

 

package com.sparta.myselectshop.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.KakaoUserInfoDto;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.UUID;

@Slf4j(topic = "KAKAO Login")
@Service
@RequiredArgsConstructor
public class KakaoService {

    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final RestTemplate restTemplate; //빨간 줄 : RestTemplate을 그냥 Builder로 만들 수 있다 
        //빨간줄 뜨는 이유 : restTemplate이 bean이 아닌 Rest Template Builder을 통해서 생성할 수 있도록 유도


    //Bean을 수동으로 등록해서 관리하는 방법

    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);


        //3. 필요 시에 회원 가입
        User kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo); //카카오 사용자 정보

        //4. JWT 토큰 반환
        String createToken = jwtUtil.createToken(kakaoUser.getUsername(), kakaoUser.getRole());

        return createToken;
    }

    
    //kakaoLogin에서 받은 인증 코드로 토큰 요청
    private String getToken(String code) throws JsonProcessingException {

        log.info("인가코드 : "+code);

        // 요청 URL 만들기
        URI uri = UriComponentsBuilder
                .fromUriString("https://kauth.kakao.com") //여긴 정해진 주소를 넣기
                .path("/oauth/token")
                .encode()
                .build()
                .toUri();

        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        //kakao Developers에서 요청한대로 http 형태를 만들어서 Rest template으로 API를 요청함
        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", "13f0c1acd35319458b5893b3dbba382d"); //여기에 RESTAPI 코드 다시 넣어주기
        body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
        body.add("code", code); //받은 인가코드 넣기

        RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                .post(uri) //body가 있으니까 POST
                .headers(headers)
                .body(body);

        // HTTP 요청 보내기
        ResponseEntity<String> response = restTemplate.exchange( //http 요청이 카카오 서버 쪽으로 감
                requestEntity,
                String.class
        ); //ResponseEntity<String>에서 반환되는 String이 토큰의 형태로 되어 있음 > 그 토큰을 한번 파싱할 것임 (.readTree(response.getBody()))

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱 = 우리가 딱 원하는 액세스 토큰만 딱 뽑아옴 
        JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
        return jsonNode.get("access_token").asText(); 
        //뽑으면 .asText()니까 String값 반환
        //맨 위의 String accessToken = getToken(code);의 String accessToken
    }




        //사용자 정보 요청 (getKakaoUserInfo 메서드)
        private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
            log.info("accessToken : "+accessToken);
        // 요청 URL 만들기
            URI uri = UriComponentsBuilder
                    .fromUriString("https://kapi.kakao.com")
                    .path("/v2/user/me")
                    .encode()
                    .build()
                    .toUri();

            // HTTP Header 생성
            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "Bearer " + accessToken);
            headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

            RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                    .post(uri) //우리가 url 만든 거 보내주기
                    .headers(headers)
                    .body(new LinkedMultiValueMap<>()); //body는 따로 보내줄 필요가 없어서 new~

            // HTTP 요청 보내기
            ResponseEntity<String> response = restTemplate.exchange(
                    requestEntity,
                    String.class //String 타입으로 받아옴
            );

            //Long 타입으로 해당하는 id 받아오는 방법
            JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());

            //해댱하는 id값들을 받아올 수 있음
            Long id = jsonNode.get("id").asLong();


            //properties 중에서 nickname 값을 가져옴
            String nickname = jsonNode.get("properties")
                    .get("nickname").asText();

            //properties 중에서 email 값을 가져옴
            String email = jsonNode.get("kakao_account")
                    .get("email").asText();

            log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
            
            //KakaoUserInfoDto에 값을 넣어줌
            return new KakaoUserInfoDto(id, nickname, email);
        }

    private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
        // DB 에 중복된 Kakao Id 가 있는지 확인
        Long kakaoId = kakaoUserInfo.getId(); //카카오 아이디 가져오기

        //user테이블에서 넣어준 kakaoId가 있으면 회원가입이 된거고 없으면 회원 가입을 한 적이 없는 것임
        User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);

        if (kakaoUser == null) {
            // 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
            String kakaoEmail = kakaoUserInfo.getEmail();
            User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
            if (sameEmailUser != null) {
                kakaoUser = sameEmailUser;
                // 기존 회원정보에 카카오 Id 추가
                kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
            } else {
                // 신규 회원가입
                // password: random UUID
                String password = UUID.randomUUID().toString();
                String encodedPassword = passwordEncoder.encode(password);

                // email: kakao email
                String email = kakaoUserInfo.getEmail();

                kakaoUser = new User(kakaoUserInfo.getNickname(), encodedPassword, email, UserRoleEnum.USER, kakaoId);
            }

            userRepository.save(kakaoUser);
        }
        return kakaoUser;
    }

}

 

User.entity로 가기 (User table) 

package com.sparta.myselectshop.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;

    //카카오 아이디 필드 추가
    private Long kakaoId;

    public User(String username, String password, String email, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.role = role;
    }

    //카카오 아이디랑 카카오 사용자 정보로 UserEntity 객체를 만드는 생성자 하나 추가
    public User(String username, String password, String email, UserRoleEnum role, Long kakaoId) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.role = role;
        this.kakaoId =kakaoId;
    }

    //카카오 아이디를 업데이트하는 메소드
    public User kakaoIdUpdate(Long kakaoId) {
        this.kakaoId = kakaoId;
        return this;
    }
}

 

UserRepository

package com.sparta.myselectshop.repository;

import com.sparta.myselectshop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
    Optional<User> findByEmail(String email);

    //추가
    Optional<User> findByKakaoId(Long kakaoId);
}

 

 

kakaoService

package com.sparta.myselectshop.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.KakaoUserInfoDto;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.UUID;

@Slf4j(topic = "KAKAO Login")
@Service
@RequiredArgsConstructor
public class KakaoService {

    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final RestTemplate restTemplate; //빨간 줄 : RestTemplate을 그냥 Builder로 만들 수 있다 
        //빨간줄 뜨는 이유 : restTemplate이 bean이 아닌 Rest Template Builder을 통해서 생성할 수 있도록 유도


    //Bean을 수동으로 등록해서 관리하는 방법

    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);


        //3. 필요 시에 회원 가입
        User kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo); //카카오 사용자 정보

        //4. JWT 토큰 반환
        String createToken = jwtUtil.createToken(kakaoUser.getUsername(), kakaoUser.getRole());

        return createToken;
    }

    
    //kakaoLogin에서 받은 인증 코드로 토큰 요청
    private String getToken(String code) throws JsonProcessingException {

        log.info("인가코드 : "+code);

        // 요청 URL 만들기
        URI uri = UriComponentsBuilder
                .fromUriString("https://kauth.kakao.com") //여긴 정해진 주소를 넣기
                .path("/oauth/token")
                .encode()
                .build()
                .toUri();

        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        //kakao Developers에서 요청한대로 http 형태를 만들어서 Rest template으로 API를 요청함
        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", "13f0c1acd35319458b5893b3dbba382d"); //여기에 RESTAPI 코드 다시 넣어주기
        body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
        body.add("code", code); //받은 인가코드 넣기

        RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                .post(uri) //body가 있으니까 POST
                .headers(headers)
                .body(body);

        // HTTP 요청 보내기
        ResponseEntity<String> response = restTemplate.exchange( //http 요청이 카카오 서버 쪽으로 감
                requestEntity,
                String.class
        ); //ResponseEntity<String>에서 반환되는 String이 토큰의 형태로 되어 있음 > 그 토큰을 한번 파싱할 것임 (.readTree(response.getBody()))

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱 = 우리가 딱 원하는 액세스 토큰만 딱 뽑아옴 
        JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
        return jsonNode.get("access_token").asText(); 
        //뽑으면 .asText()니까 String값 반환
        //맨 위의 String accessToken = getToken(code);의 String accessToken
    }




        //사용자 정보 요청 (getKakaoUserInfo 메서드)
        private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
            log.info("accessToken : "+accessToken);
        // 요청 URL 만들기
            URI uri = UriComponentsBuilder
                    .fromUriString("https://kapi.kakao.com")
                    .path("/v2/user/me")
                    .encode()
                    .build()
                    .toUri();

            // HTTP Header 생성
            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "Bearer " + accessToken);
            headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

            RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                    .post(uri) //우리가 url 만든 거 보내주기
                    .headers(headers)
                    .body(new LinkedMultiValueMap<>()); //body는 따로 보내줄 필요가 없어서 new~

            // HTTP 요청 보내기
            ResponseEntity<String> response = restTemplate.exchange(
                    requestEntity,
                    String.class //String 타입으로 받아옴
            );

            //Long 타입으로 해당하는 id 받아오는 방법
            JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());

            //해댱하는 id값들을 받아올 수 있음
            Long id = jsonNode.get("id").asLong();


            //properties 중에서 nickname 값을 가져옴
            String nickname = jsonNode.get("properties")
                    .get("nickname").asText();

            //properties 중에서 email 값을 가져옴
            String email = jsonNode.get("kakao_account")
                    .get("email").asText();

            log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
            
            //KakaoUserInfoDto에 값을 넣어줌
            return new KakaoUserInfoDto(id, nickname, email);
        }

    private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
        // DB 에 중복된 Kakao Id 가 있는지 확인
        Long kakaoId = kakaoUserInfo.getId(); //카카오 아이디 가져오기

        //user테이블에서 넣어준 kakaoId가 있으면 회원가입이 된거고 없으면 회원 가입을 한 적이 없는 것임 (없으면 .orElse (null) -> null이 들어감)
        User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);

        //회원가입 정보가 없어서 null이 뜨면 아래 회원가입 시켜주는 코드가 실행됨
        if (kakaoUser == null) {
            //카카오 소셜 로그인으로 로그인한 전적 없음

            //우리 사이트에서 그냥 바로 회원가입 했으나, kakaoEmail로 회원가입한 사람
            //근데 그 kakaoEmail로 카카오 로그인을 시도할 경우,

            // 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
            // 이 경우 같은 회원이라고 판단하자
            String kakaoEmail = kakaoUserInfo.getEmail();
            User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
            if (sameEmailUser != null) { //카카오 이메일을 통해 일반 로그인한 유저 있음
                kakaoUser = sameEmailUser;
                // 기존 회원정보에 카카오 Id 추가
                kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
            } else {
                // 진짜 신규 카카오  회원가입
                // password: random UUID
                String password = UUID.randomUUID().toString();
                String encodedPassword = passwordEncoder.encode(password); //인코딩해서 암호화해서 넣어줌

                // email: kakao email
                String email = kakaoUserInfo.getEmail();

                kakaoUser = new User(kakaoUserInfo.getNickname(), encodedPassword, email, UserRoleEnum.USER, kakaoId); //일반 사용자 권한

            }

            userRepository.save(kakaoUser); //상황에 따라 update 또는 save이기 때문에 @Transactional 걸어주지 않고 .save() 사용했음
        }
        return kakaoUser;
    }

}

 

 

에러 나는 원인

쿠키 밸류는 공백이 들어가면 안됨 근데 Bearer 다음에 공백이 들어가면서 에러남

Controller 에서 substring 해주기

 

 

 

userController 

package com.sparta.myselectshop.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sparta.myselectshop.dto.SignupRequestDto;
import com.sparta.myselectshop.dto.UserInfoDto;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.security.UserDetailsImpl;
import com.sparta.myselectshop.service.FolderService;
import com.sparta.myselectshop.service.KakaoService;
import com.sparta.myselectshop.service.UserService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/api")
public class UserController {

    private final UserService userService;
    private final FolderService folderService;
    private final KakaoService kakaoService;

    @GetMapping("/user/login-page")
    public String loginPage() {
        return "login";
    }

    @GetMapping("/user/signup")
    public String signupPage() {
        return "signup";
    }

    @PostMapping("/user/signup")
    public String signup(@Valid SignupRequestDto requestDto, BindingResult bindingResult) {
        // Validation 예외처리
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        if (fieldErrors.size() > 0) {
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                log.error(fieldError.getField() + " 필드 : " + fieldError.getDefaultMessage());
            }
            return "redirect:/api/user/signup";
        }

        userService.signup(requestDto);

        return "redirect:/api/user/login-page";
    }

    // 회원 관련 정보 받기
    @GetMapping("/user-info")
    @ResponseBody
    public UserInfoDto getUserInfo(@AuthenticationPrincipal UserDetailsImpl userDetails) {
        String username = userDetails.getUser().getUsername();
        UserRoleEnum role = userDetails.getUser().getRole();
        boolean isAdmin = (role == UserRoleEnum.ADMIN);

        return new UserInfoDto(username, isAdmin);
    }

    @GetMapping("/user-folder")
    public String getUserInfo(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {

        model.addAttribute("folders", folderService.getFolders(userDetails.getUser()));
        return "index :: #fragment";
    }

    //카카오에서 보내는 인가 코드는 쿼리 스트링 방식으로 넘어오고, @RequestParam으로 받을 수 있음
    //완료되면 이 jwt를 생성해서 쿠키를 우리가 직접 만들어서 브라우저에게 시킬 것임
    //이전에 회원 기능 구현할 때는 header에 jwt 넣었음
    //우리 client 구조 상 카카오 login 했을 때는 우리가 직접 쿠키를 생성해서 jwt에 넣은 다음에 브라우저에 자동으로 set될 수 있도록 구현해보기
    @GetMapping("/user/kakao/callback")
    public String kakaoLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
        //kakaoService 단의 kakoLogin 메소드
        String token = kakaoService.kakaoLogin(code); //String token = jwt token
        //빨간 줄 : 예외를 시그니처에 추가 누르기
        //kakao에서 받아온 token을 쿠키에 넣어주고 reponse 객체 넣어주는 작업
        Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, token.substring(7));
        cookie.setPath("/");
        response.addCookie(cookie); //브라우저의 jwt 값이 set 될 것임


        return "redirect:/";

    }

}