Updated:

 다음은 카카오 로그인을 위한 문서이다.
https://developers.kakao.com/docs/latest/ko/kakaologin/common

 다음은 카카오 로그인 과정이다.

  • 인가 코드 받기
    • 서비스 서버가 카카오 인증 서버로 인가 코드 받기를 요청한다.
    • 카카오 인증 서버가 사용자에서 인증을 요청한다.
    • 사용자가 카카오 계정으로 로그인한다.
    • 카카오 인증 서버가 사용자에게 동의 화면을 출력하여 인가를 위한 사용자 동의를 요청한다.
    • 사용자가 필수 동의항목과, 이 외의 원하는 동의항목에 동의한 뒤 “동의하고 계속하기” 버튼을 누른다.
    • 카카오 인증 서버는 서비스 서버의 Redirect URI로 인가 코드를 전달한다.
  • 토큰 받기
    • 서비스 서버가 Redirect URI로 전달받은 인가 코드로 토큰 받기를 요청한다.
    • 카카오 인증 서버가 토큰을 발급해 서비스 서버에 전달한다.
  • 사용자 로그인 처리
    • 서비스 서버가 발급받은 엑세스 토큰으로 사용자 정보 가져오기를 요청해 사용자의 회원번호 및 정보를 조회하여 서비스 회원인지 확인한다.
    • 서비스 회원 정보 확인 결과에 따라 서비스 로그인 또는 회원 가입한다.
    • 이 외 서비스에서 필요한 로그인 절차를 수행한 후, 카카오 로그인한 사용자의 서비스 로그인 처리를 완료한다.

Kakao Developer Setting

 개발하기 전, Kakao Develoer(https://developers.kakao.com/)에서 애플리케이션을 추가해줘야 한다.

 이후 도메인을 추가하고 카카오 로그인을 활성화해준뒤, Redirect URI를 설정해준다..

동의 항목 설정

 사용자 정보를 요청하기 위해 동의 항목을 설정해준다.

  • 해당 프로젝트에서는 닉네임은 로그인 이후에 받을 예정이기 때문에 프로필 사진만 필수 동의 상태로 바꿔준다.

로그인 요청

 카카오 로그인 Rest API 문서를 보면 다음과 같은 요청을 하면 인가 코드를 받을 수 있다고 한다.

https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}
  • REDIRECT_URI: Kakao Developer Setting에서 설정한 http://localhost:8080/auth/login/kakao
  • REST_API_KEY: 앱 설정의 앱 키에서 확인 가능

https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=http://localhost:8080/auth/login/kakao로 접속해보면 다음과 같은 화면을 확인할 수 있다.

KakaoLoginPageController

 로그인 페이지를 반환해줄 kakaoLoginPageController.java를 작성한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

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

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

    @Value("${kakao.auth.redirect}")
    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";
    }
}

 Thymeleaf 의존성을 추가해준 뒤, resources/templates에 login.html을 작성한다. 이때 thymeleaf는 resouces/templates에 있는 html 파일을 사용하기 때문에 다른 디렉토리에 넣어두어서는 안된다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<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="/assets/kakao_login_medium_narrow.png">
    </a>
</div>

</body>
</html>

localhost:8080/login/page에 접속하면 다음과 같은 화면이 출력된다.

 카카오 로그인을 클릭하면 다음과 같은 화면이 출력되고, 로그인을 하면 등록해둔 Redirect URI에 파라미터로 code와 함께 Redirect되는 것을 확인할 수 있다.

  • http://localhost:8080/auth/login/kakao?code=TGBJ5W5an5fiZkpj37mtaNmD2rXZdK2-XTuWz4jfJNvF46TKQMSR5QAAAAQKFxDvAAABlf-6buBSGUcvaFb1Eg

Redirect URI와 같이 code 전달받기

 이제 Redirect된 URI에 전달된 code를 가져와본다.

KakaoLoginController

 먼저 KakaoLoginController를 대략 작성해본다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("")
public class KakaoLoginController {

    @GetMapping("/auth/login/kakao")
    public ResponseEntity<?> KakaoLogin(@RequestParam("code") String code) {
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
  • 등록해둔 URI에 GetMapping을 해두면 파라미터 code와 함께 받을 수 있다.

code를 통해 토큰 발급 요청

 카카오로부터 받은 code를 카카오에 토큰 발급 요청을 하면, 사용자 정보가 담겨있는 토큰을 받을 수 있다.

  • https://kauth.kakao.com/oauth/token 으로 POST 요청을 보내면, 토큰을 받을 수 있다.

 따라서 카카오로부터 받은 ResponseDTO를 생성한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

    // 역직렬화를 위해 JsonProperty를 이용
    @JsonProperty("token_type") // 토큰 타입
    public String tokenType;
    @JsonProperty("access_token")   // 사용자 엑세스 토큰 값
    public String accessToken;
    @JsonProperty("id_token")   // ID 토큰 값
    public String idToken;
    @JsonProperty("expires_in") // 엑세스 토큰과 ID 토큰의 만료 시간(초)
    public String expiresIn;
    @JsonProperty("refresh_token")  // 사용자 리프레시 토큰 값
    public String refreshToken;
    @JsonProperty("refresh_token_expires_in")   // 리프레시 토큰 만료 시간(초)
    public String refreshTokenExpiresIn;
    @JsonProperty("scope")  // 인증된 사용자의 정보 조회 권한 범위
    public String scope;
}

KakaoService

Spring Reactive Web dependency를 추가해준다. 이후 KakaoService를 작성한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.service;

import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dto.KakaoTokenResponseDto;
import io.netty.handler.codec.http.HttpHeaderValues;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@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.auth.client}") 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()
                .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());
        log.info(" [Kakao Service] Id Token ------> {}", kakaoTokenResponseDto.getIdToken());
        log.info(" [Kakao Service] Scope ------> {}", kakaoTokenResponseDto.getScope());

        return kakaoTokenResponseDto.getAccessToken();
    }

}
  • Webclient로 HTTP 요청을 구현했다. 각 서비스마다 Custom Exception이 있다면 onStatue 메서드에 지정하면 된다.
  • HTTP 요청을 받아모녀 .retrieve() 메서드 부터, Request Body 내용이 미리 지정해둔 KakaoTokenResponseDto에 json이 직렬화되어 들어가게 된다.

https://kauth.kakao.com/oauth/token?grant_type=authorizarion_code&client_id=${client_id}&code={code}로 Post 요청을 보내면, 받은 Response의 Body에 아래 내용을 받을 수 있다.

KakaoLoginController

 이제 구현한 service를 controller에서 연동한다. KakaoLoginController에 내용을 추가한다.

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("")
public class KakaoLoginController {

    private final KakaoService kakaoService;
    
    @GetMapping("/auth/login/kakao")
    public ResponseEntity<?> KakaoLogin(@RequestParam("code") String code) {
        String accessToken = kakaoService.getAccessTokenFromKakao(code);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
  • 이제 code를 이용해서 accessToken을 받아올 수 있다.

Token으로 사용자 정보 가져오기

 이제 카카오에서 받은 access token으로 사용자 정보를 가져와본다.

KakaoService

 먼저 사용자의 정보를 담을 KakaoUserInfoResponseDto를 추가한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.HashMap;

@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;
    }
}

 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)
                .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                .retrieve()
                .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] Id Token ------> {}", userInfo.getKakaoAccount().getProfile().getProfileImageUrl());

        return userInfo;
    }
  • https://kapi.kakao.com/v2/user/me에 헤더 Authorization에 Bearer token을 추가해서 GET 요청을 보내면, Response에 아래와 같은 Body를 받을 수 있다.

KakaoLoginController

 최종적으로 KakaoLoginController는 다음과 같다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.controller;

import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dto.KakaoUserInfoResponseDto;
import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.service.KakaoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("")
public class KakaoLoginController {

    private final KakaoService kakaoService;

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

        KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken);

        // 여기에 서버 사용자 로그인(인증) 또는 회원가입 로직 추가
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

회원가입, 로그인

 로그인 이후 회원가입을 통해 카카오에서 제공하는 정보 외에 내용을 받을려고한다. 그래서 다음과 같이 Member를 정의해준다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.entity;

import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dto.SignupRequestDto;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name="members")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {

    @Id
    private Long kakaoId;

    private String name;
    private String location;

    @Enumerated(EnumType.STRING)
    private Role role;

    @Column(name = "profile_image_url")
    private String profileImageUrl;

    public enum Role{
        CONSUMER, CORPORATE
    }

    public static Member create(SignupRequestDto dto) {
        return Member.builder()
                .kakaoId(dto.getKakaoId())
                .name(dto.getNickname())
                .location(dto.getLocation())
                .role(Member.Role.valueOf(dto.getRole().toUpperCase()))
                .profileImageUrl(dto.getProfileImageUrl())
                .build();
    }

}

 또한 Member와 관련된 내용을 DB에 저장하기위해 MemerReposiroty를 작성한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dao;

import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {
    Optional<Member> findByKakaoId(Long kakaoId);
}
  • findBykakaoId: kakaoId를 통해 DB에서 Member를 찾는다.

  이제 MemberService와 MemberServiceImpl을 작성한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.service;

import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dao.MemberRepository;
import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dto.SignupRequestDto;
import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.entity.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    @Override
    public Member signup(SignupRequestDto dto) {
        return memberRepository.save(Member.create(dto));
    }

    @Override
    public Optional<Member> findByKakaoId(Long kakaoId) {
        return memberRepository.findByKakaoId(kakaoId);
    }

}

 이제 회원가입을 위한 controller를 작성한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.controller;

import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.dto.SignupRequestDto;
import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.entity.Member;
import com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/member")
public class MemberController {

    private final MemberService memberService;

    @PostMapping("/signup")
    public ResponseEntity<Member> signup(@RequestBody SignupRequestDto dto) {
        try {
            Member savedMember = memberService.signup(dto);
            return ResponseEntity.ok(savedMember);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
        }
    }
}

 이제 테스트를 위해 SignupPageController를 작성해 Thymleaf를 이용해 테스트한다.

package com.example.seoulpublicdata2025backend.domain.kakaoSocialLogin.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/signup")
public class SignupPageController {

    @GetMapping
    public String signupPage(@RequestParam("kakaoId") Long kakaoId, @RequestParam("profileUrl") String profileUrl, Model model) {
        model.addAttribute("kakaoId", kakaoId);
        model.addAttribute("profileUrl", profileUrl);
        return "signup";
    }
}
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>회원가입</title>
</head>
<body>

<h2>추가 정보 입력</h2>

<!-- 🔥 프로필 이미지 미리보기 -->
<img th:src="${profileUrl}" alt="카카오 프로필 이미지" width="100" height="100" style="border-radius: 50%; margin-bottom: 10px;">

<form id="signupForm">
    <input type="text" id="nickname" name="nickname" placeholder="닉네임" required>
    <input type="text" id="location" name="location" placeholder="거주지" required>

    <select id="role" name="role" required>
        <option value="">사용자 유형 선택</option>
        <option value="CONSUMER">소비자</option>
        <option value="CORPORATE">기업</option>
    </select>

    <!-- 🔥 필수 데이터 (카카오 ID와 프로필 이미지 URL) -->
    <input type="hidden" id="kakaoId" name="kakaoId" th:value="${kakaoId}">
    <input type="hidden" id="profileImageUrl" name="profileImageUrl" th:value="${profileUrl}">

    <button type="submit">가입</button>
</form>

<script>
    document.getElementById('signupForm').addEventListener('submit', async function (e) {
        e.preventDefault();

        const kakaoId = document.getElementById('kakaoId').value;
        const nickname = document.getElementById('nickname').value;
        const location = document.getElementById('location').value;
        const role = document.getElementById('role').value;
        const profileImageUrl = document.getElementById('profileImageUrl').value;

        const data = {
            kakaoId: kakaoId,
            nickname: nickname,
            location: location,
            role: role,
            profileImageUrl: profileImageUrl
        };

        try {
            const response = await fetch('/member/signup', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            });

            if (response.ok) {
                alert('회원가입 성공!');
                window.location.href = '/'; // 필요시 리다이렉트 수정
            } else {
                alert('회원가입 실패: ' + response.status);
            }
        } catch (err) {
            console.error('에러 발생:', err);
            alert('서버 오류');
        }
    });
</script>

</body>
</html>

 이제 회원가입이 가능해지고, 만약 회원가입한 회원이면 main 페이지로 가게된다.

댓글남기기