์นด์นด์ค๋ก ์์ํ๊ธฐ
ํ์ฌ Jira, Slack๊ณผ ์ ์ฌํ ํ์ ํด ํ๋ซํผ ํ๋ก์ ํธ๋ฅผ ์งํํ๊ณ ์๋ค.
์ด ํ๋ก์ ํธ์์๋ JWT ์์ฒด ํ์๊ฐ์ /๋ก๊ทธ์ธ๊ณผ ํจ๊ป OAuth 2.0์ ํ์ฉํ ์นด์นด์ค ์์ ๋ก๊ทธ์ธ๊น์ง ๋ชจ๋ ์ง์ํ๋๋ก ์ค๊ณํ๊ณ ์๋ค.
์ค๋ ํฌ์คํ ์์, ์นด์นด์ค ์์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง (OAuth 2.0์ ์ธ์ฆ ํ๋ฆ๋ถํฐ, ์ค์ API ์ฐ๋๊ณผ ์๋ฒ-ํ๋ก ํธ ์ฒ๋ฆฌ๊น์ง)
์ง์ ๊ฒฝํํ๋ฉฐ ๋ฐฐ์ด ๋ด์ฉ์ ์ฐจ๊ทผ์ฐจ๊ทผ ์ ๋ฆฌํด๊ฐ ์์ ์ด๋ค.
๐ OAuth 2.0 ์ด๋?
OAuth 2.0์ ์น/์ฑ ์๋น์ค๊ฐ ์ฌ์ฉ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ง์ ์์ง ์๊ณ ๋ค๋ฅธ ์๋น์ค(์นด์นด์ค, ๋ค์ด๋ฒ, ๊ตฌ๊ธ ๋ฑ)์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ด์ฉํด ์์ ํ๊ฒ "์ธ์ฆ" ๋ฐ "๊ถํ ์์"์ ํ ์ ์๊ฒ ํด์ฃผ๋ ์ ๊ณ ํ์ค ํ๋กํ ์ฝ์ด๋ค.
โ ์ ํ์ํ ๊น?
- ๋น๋ฐ๋ฒํธ๋ฅผ 3rd-party ์๋น์ค์ ์ง์ ์ ๋ ฅํ์ง ์๋๋ค. → ๋ณด์ UP
- ์นด์นด์ค, ๊ตฌ๊ธ ๋ฑ์์ ์ธ์ฆํ๋ฉด ๋ด ์๋น์ค์์๋ ๊ฐ๋จํ๊ฒ ์ถ๊ฐ ์ ๋ณด๋ง ๋ฐ์์ ํ์๊ฐ์ /๋ก๊ทธ์ธ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค.
๐ OAuth 2.0 ๊ธฐ๋ณธ ๋์ ํ๋ฆ
OAuth 2.0 ํ๋ก์ฐ๋ [Client(์ฐ๋ฆฌ ์๋น์ค)] - [์ฌ์ฉ์] - [3rd-party(์นด์นด์ค ๋ฑ ์ธ์ฆ ์ ๊ณต์)] ์ด ์ธ ์ฃผ์ฒด ์ฌ์ด์์ ์๋ ๋จ๊ณ๋ก ๋์ํ๋ค.
1. ์ธ๊ฐ ์์ฒญ (Authorization Request)
- ์ฌ์ฉ์๊ฐ "์นด์นด์ค๋ก ๋ก๊ทธ์ธ" ๋ฒํผ์ ํด๋ฆญํ๋ค.
- ์ฐ๋ฆฌ ์๋น์ค๊ฐ ์นด์นด์ค ์ธ์ฆ ์๋ฒ๋ก ๋ฆฌ๋ค์ด๋ ํธ
- ์์ฒญ ํ๋ผ๋ฏธํฐ(ํด๋ผ์ด์ธํธ ID, ๋ฆฌ๋ค์ด๋ ํธ URL, scope ๋ฑ)๋ฅผ ํฌํจ
GET https://kauth.kakao.com/oauth/authroize?client_id=xxx
&redirect_url=xxx
&response_type=code
&scope=profile,email
2. ์ฌ์ฉ์ ์ธ์ฆ & ์ธ๊ฐ
- ์ฌ์ฉ์๋ ์นด์นด์ค ๋ก๊ทธ์ธ ํ์ด์ง์์ ๋ก๊ทธ์ธ(๋ก๊ทธ์ธ๋์ด์์ผ๋ฉด ๋ฐ๋ก ์งํ)
- ์๋น์ค์ ๋ํด ์ฌ์ฉ์๊ฐ ์ ๋ณด๋ฅผ ๋์ํ ์ง ์ฌ๋ถ ์ ํ → ๋์/๊ฑฐ๋ถ
- ๋์ํ๋ฉด ์นด์นด์ค๊ฐ ์ธ๊ฐ ์ฝ๋(authorization code)๋ฅผ ์์ฑ
3. ์ธ๊ฐ ์ฝ๋ ์ฝ๋ฐฑ(Redirect)
- ์นด์นด์ค๋ ๋ฆฌ๋ค์ด๋ ํธ URL(์ฐ๋ฆฌ ๋ฐฑ์๋)์ code = ์ธ๊ฐ์ฝ๋ ํ๋ผ๋ฏธํฐ๋ก GET ์์ฒญ์ ๋ณด๋.
GET /auth/kakao/callback?code=abcdef...
4. ํ ํฐ ๋ฐ๊ธ (Access Token Exchange)
- ์ฐ๋ฆฌ ์๋ฒ(๋ฐฑ์๋)๊ฐ ๋ฐ์ ์ธ๊ฐ์ฝ๋๋ก ์นด์นด์ค์๊ฒ POST ์์ฒญ
- ์นด์นด์ค๋ ์ธ๊ฐ์ฝ๋ ํ์ธ ํ Access Token ๋๋ Refresh Token(์ ํ)์ ์ ๋ฌํ๋ค.
POST https://kauth.kakao.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=xxx
&redirect_uri=xxx
&code=abcdef...
5. API ํธ์ถ(์ ์ ์ ๋ณด ์กฐํ ๋ฑ)
- ๋ฐ์ Access Token์ ์ฌ์ฉํด์ ์นด์นด์ค Open API ํธ์ถ(ex. ์ ์ ํ๋กํ/์ด๋ฉ์ผ ๋ฑ)
GET https://kapi.kakao.com/v2/user/me
Authorization: Bearer {access_token}
6. ์๋น์ค ์์ฒด ๋ก๊ทธ์ธ/ํ์๊ฐ์ ์ฒ๋ฆฌ
- ์ด์ ๋ถํด ์ฐ๋ฆฌ ์๋น์ค์ ๋ก์ง์ผ๋ก ๋์.
- AccessToken์ผ๋ก ๊ฐ์ ธ์จ ํ๋กํ ์ ๋ณด(KAKAO ID, EMAIL ๋ฑ)๋ก
- ๊ธฐ์กดํ์์ด๋ฉด ๋ฐ๋ก ๋ก๊ทธ์ธ (JWT ๋ฐ๊ธ)
- ์ ๊ทํ์์ด๋ฉด ์ถ๊ฐ ์ ๋ณด ์ ๋ ฅ(ํ์๊ฐ์ )
- AccessToken์ผ๋ก ๊ฐ์ ธ์จ ํ๋กํ ์ ๋ณด(KAKAO ID, EMAIL ๋ฑ)๋ก
๐ป ์ค์ ์ฝ๋์ ํจ๊ป ๋ณด๋ ์นด์นด์ค ์์ ๋ก๊ทธ์ธ ๊ตฌํ ์์
0. ํ๋ก์ ํธ ํ๊ฒฝ
- BE: Spring Boot + JPA + Spring Security + MySQL
- FE: Next.js (React), TypeScript
1. ์นด์นด์ค ๊ฐ๋ฐ์ ์ผํฐ ์ ํ๋ฆฌ์ผ์ด์ ๋ฑ๋ก
1-1. ์นด์นด์ค ๊ฐ๋ฐ์ ์ผํฐ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐ
1-2. REST API Key ๋ฐ๊ธ
1-3. ๋์ ํญ๋ชฉ ์ค์
1-4. Redirect URI ์ค์
2. ํ๊ฒฝ ์ค์ (yml, env, gradle)
2-1. build.gradle (dependencies ์์กด์ฑ ์ถ๊ฐ)
- ์ฃผ์ ์์กด์ฑ
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'mysql:mysql-connector-java'
implementation 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
2-2. application.yml (local)
spring:
datasource:
url: jdbc:mysql://localhost:3306/issue_mate
username: ENC(${DB_USER_ENC})
password: ENC(${DB_PW_ENC})
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
# show_sql: true
format_sql: true
default_batch_fetch_size: 100
open-in-view: false
data:
redis:
host: localhost
port: 6379
#api_url: http://localhost:8080
next_public_base_url: http://localhost:3000
##kakao login
kakao:
client:
id: ${KAKAO_RESTAPIKEY}
redirect:
uri: ${KAKAO_REDIRECTURI}
2-3. .env
KAKAO_CLIENT_ID=์ฌ๊ธฐ์RESTAPIํค
KAKAO_REDIRECT_URI=http://localhost:8080/auth/kakao/callback
3. Security Config ์ค์
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/auth/**", "/").permitAll()
.anyRequest().authenticated();
return http.build();
}
}
- /auth/** ๊ฒฝ๋ก ๋ชจ๋ permitAll() → ์นด์นด์ค ์ฝ๋ฐฑ/ํ์๊ฐ์ ๋๊ตฌ๋ ์ ๊ทผ
- ๋๋จธ์ง ๊ฒฝ๋ก๋ ์ธ์ฆ ํ์
4. BE: Entity / DTO ์ ์
4-1. USER Entity
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id @GeneratedValue
@Column(name = "user_id")
private Long id;
@Column(nullable = false, unique = true)
private String userEmail;
private String password;
private String name;
private String phone;
private String provider; // "KAKAO", "LOCAL" ๋ฑ
private String providerId; // ์นด์นด์ค ๊ณ ์ id
private boolean termsAgreed;
private String profile;
}
4-2. ์นด์นด์ค ๊ด๋ จ DTO
- KakaoLoginResultDto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class KakaoLoginResultDto {
private boolean newUser; // ์ ๊ทํ์ ์ฌ๋ถ
private String jwt; // ๊ธฐ์กดํ์์ด๋ฉด ๋ฐ๊ธ๋ JWT
private String kakaoId;
private String email;
private String name;
}
- KakaoSocialSignupDto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class KakaoSocialSignUpdto {
private String kakaoId;
private String email;
private String name;
private String phone;
private boolean termsAgreed;
}
- KakaoRegisterResponseDto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class KakaoRegisterResponseDto {
private String token;
private String message;
}
5. BE: Service ๊ตฌํ
5-1. kakaoLoginResult(String code) Method
/**
* ์นด์นด์ค ๋ก๊ทธ์ธ: code๋ก access token ๋ฐ๊ธ → ์ ์ ์ ๋ณด ์กฐํ → ํ์์ฌ๋ถ ํ๋ณ
* @param code ์ธ๊ฐ์ฝ๋
*/
@Transactional(readOnly = true)
public KakaoLoginResultDto kakaoLoginResult(String code) {
log.info("[์นด์นด์ค ๋ก๊ทธ์ธ] code๋ก accessToken ๋ฐ ํ๋กํ ์กฐํ");
// (1) ์ธ๊ฐ์ฝ๋๋ก access token ์์ฒญ
String accessToken = getKakaoAccessToken(code);
// (2) access token์ผ๋ก ์ ์ ์ ๋ณด ์กฐํ
KakaoUserInfoDto userInfo = getKakaoUserInfo(accessToken);
log.info("[์นด์นด์ค ๋ก๊ทธ์ธ] userInfo: kakaoId={}, email={}, name={}", userInfo.getKakaoId(), userInfo.getEmail(), userInfo.getName());
// (3) DB์์ ํด๋น provider/providerId ์ ์ ์กฐํ
User user = userRepository.findByProviderAndProviderId("KAKAO", userInfo.getKakaoId()).orElse(null);
if (user == null) {
// ์ ๊ท ํ์ → ์ถ๊ฐ์ ๋ณด ์
๋ ฅ ํ์
return new KakaoLoginResultDto(true, null, userInfo.getKakaoId(), userInfo.getEmail(), userInfo.getName());
}
// ๊ธฐ์กด ํ์ → JWT ๋ฐ๊ธ
String jwt = jwtProvider.generateToken("access", user.getUserEmail(), 360000L);
return new KakaoLoginResultDto(false, jwt, null, null, null);
}
- ์ธ๊ฐ์ฝ๋๋ก ํ ํฐ/์ ์ ์กฐํ ํ ์ ๊ท/๊ธฐ์กด ํ์ ๋ถ๊ธฐ
5-2. kakaoRegister(KakaoSocialSignupDto) Method
/**
* ์นด์นด์ค ์ถ๊ฐ์ ๋ณด ์
๋ ฅ ํ์๊ฐ์
*/
@Transactional
public String kakaoRegister(KakaoSocialSignUpdto signUpdto) {
log.info("[์นด์นด์ค ํ์๊ฐ์
] ์๋: kakaoId={}, email={}, name={}", signUpdto.getKakaoId(), signUpdto.getEmail(), signUpdto.getName());
// ์ค๋ณต๊ฐ์
๋ฐฉ์ง
if (userRepository.findByProviderAndProviderId("KAKAO", signUpdto.getKakaoId()).isPresent()) {
throw new IllegalArgumentException("์ด๋ฏธ ๊ฐ์
๋ ์นด์นด์ค ๊ณ์ ์
๋๋ค.");
}
User user = createUserFromKakao(signUpdto);
userRepository.save(user);
// JWT ๋ฐ๊ธ ํ ๋ฐํ
String jwt = jwtProvider.generateToken("access", user.getUserEmail(), 360000L);
return jwt;
}
- ์ถ๊ฐ์ ๋ณด๋ฅผ ์ ๋ ฅ๋ฐ์์ User ๋ฑ๋ก, JWT ๋ฐ๊ธ
5-3. ๊ธฐํ ๋ด๋ถ ๋ฉ์๋(getKakaoAccessToken, getKakaoUserInfo, createUserFromKakao)
// --- ๋ด๋ถ ๋ฉ์๋ ---
/** ์ธ๊ฐ์ฝ๋๋ก access token ํ๋ */
private String getKakaoAccessToken(String code) {
String tokenUrl = "https://kauth.kakao.com/oauth/token";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", clientId);
params.add("redirect_uri", redirectUri);
params.add("code", code);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.postForEntity(tokenUrl, request, String.class);
try {
JsonNode root = objectMapper.readTree(response.getBody());
String accessToken = root.get("access_token").asText();
return accessToken;
} catch (Exception e) {
throw new RuntimeException("์นด์นด์ค access token ํ์ฑ ์คํจ", e);
}
}
/** access token์ผ๋ก ์นด์นด์ค ์ ์ ์ ๋ณด ์กฐํ */
private KakaoUserInfoDto getKakaoUserInfo(String accessToken) {
String userInfoUrl = "https://kapi.kakao.com/v2/user/me";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<?> request = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(userInfoUrl, HttpMethod.GET, request, String.class);
try {
JsonNode root = objectMapper.readTree(response.getBody());
JsonNode kakaoAccount = root.get("kakao_account");
String kakaoId = root.get("id").asText();
String email = kakaoAccount.get("email").asText();
String name = kakaoAccount.get("profile").get("nickname").asText();
return new KakaoUserInfoDto(email, name, kakaoId, null);
} catch (Exception e) {
throw new RuntimeException("์นด์นด์ค ์ฌ์ฉ์ ์ ๋ณด ํ์ฑ ์คํจ", e);
}
}
/** ์นด์นด์ค ์ถ๊ฐ์ ๋ณด๋ก User ์ํฐํฐ ์์ฑ */
private User createUserFromKakao(KakaoSocialSignUpdto dto) {
return User.builder()
.userEmail(dto.getEmail())
.name(dto.getName())
.phone(dto.getPhone())
.provider("KAKAO")
.providerId(dto.getKakaoId())
.termsAgreed(dto.isTermsAgreed())
.build();
}
}
- getKakaoAccessToken(String Code): ์ธ๊ฐ์ฝ๋๋ก Access Token ํ๋ - Oauth ๊ฐ๋ 4 ์ฐธ๊ณ
- getKakaoUserInfo(String accessToken): Access Token์ผ๋ก ์นด์นด์ค ์ ์ ์ ๋ณด ์กฐํ - Oauth ๊ฐ๋ 5 ์ฐธ๊ณ
- createUserFormKakao(KakaoSocialSignUpDto): ์นด์นด์ค + ์ถ๊ฐ์ ๋ณด๋ก User ์ํฐํฐ ์์ฑ
6. BE: Contoller ๊ตฌํ
6-1. GET /kakao/login
/**
* 1. ์นด์นด์ค ๋ก๊ทธ์ธ ์ธ๊ฐ URL ๋ฆฌ๋ค์ด๋ ํธ
*/
@GetMapping("/kakao/login")
public void redirectToKakao(HttpServletResponse response) throws IOException {
String url = "https://kauth.kakao.com/oauth/authorize"
+ "?client_id=" + kakaoClientId
+ "&redirect_uri=" + kakaoRedirectUri
+ "&response_type=code"
+ "&scope=account_email,profile_nickname";
response.sendRedirect(url);
}
- ์ธ๊ฐ URL ์์ฑ ํ ๋ฆฌ๋ค์ด๋ ํธ(์นด์นด์ค ๋ก๊ทธ์ธ์ฐฝ์ผ๋ก)
6-2. GET /kakao/callback
/**
* 2. ์ธ๊ฐ์ฝ๋ ์ฝ๋ฐฑ ์ฒ๋ฆฌ (๊ธฐ์กด/์ ๊ท ํ์ ๋ถ๊ธฐ)
*/
@GetMapping("/kakao/callback")
public void kakaoCallback(@RequestParam("code") String code, HttpServletResponse response) throws IOException {
try {
KakaoLoginResultDto result = kakaoAuthService.kakaoLoginResult(code);
if (result.isNewUser()) {
// ์ ๊ท ํ์ - ํ์๊ฐ์
ํผ์ผ๋ก ์ฟผ๋ฆฌ ์ ๋ฌ
String redirectUrl = String.format(
"http://localhost:3000/landing?signupNeeded=1&kakaoId=%s&email=%s&name=%s",
result.getKakaoId(), result.getEmail(), result.getName()
);
response.sendRedirect(redirectUrl);
} else {
// ๊ธฐ์กด ํ์ - ๋ฐ๋ก ํ ํฐ ๋ฆฌ๋ค์ด๋ ํธ
String redirectUrl = "http://localhost:3000/oauth-success?token=" + result.getJwt();
response.sendRedirect(redirectUrl);
}
} catch (Exception e) {
response.sendRedirect("http://localhost:3000/error?msg=" + e.getMessage());
}
}
- ์ธ๊ฐ์ฝ๋๋ก ๋ก๊ทธ์ธ ์ฒ๋ฆฌ, ์ ๊ท/๊ธฐ์กด ๋ถ๊ธฐ
6-3. POST /kakao/register
/**
* 3. ์นด์นด์ค ํ์๊ฐ์
(์ถ๊ฐ์ ๋ณด ์
๋ ฅ)
*/
@PostMapping("/kakao/register")
public ResponseEntity<?> kakaoRegister(@RequestBody KakaoSocialSignUpdto signUpdto) {
try {
String jwt = kakaoAuthService.kakaoRegister(signUpdto);
return ResponseEntity.ok().body(new KakaoRegisterResponseDto(jwt, "ํ์๊ฐ์
์ฑ๊ณต"));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(new KakaoRegisterResponseDto(null, e.getMessage()));
} catch (Exception e) {
return ResponseEntity.internalServerError().body(new KakaoRegisterResponseDto(null, "์๋ฒ ์ค๋ฅ"));
}
}
- ์ถ๊ฐ์ ๋ณด POST๋ก ํ์๊ฐ์ ์ฒ๋ฆฌ
7. FE: ์นด์นด์ค ์์ ๋ก๊ทธ์ธ ํ๋ก์ฐ (Next.js/React, TypeScript)
7-1. API ์ ํธ ํจ์ (api/auth.ts, api/register.ts)
// ์นด์นด์ค ์์
๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
export function loginWithKakao() {
window.location.href = "http://localhost:8080/auth/kakao/login";
}
// ์นด์นด์ค ํ์๊ฐ์
export async function registerKakaoUser(payload: KakaoSocialSignUpRequest): Promise<KakaoSocialSignUpResponse> {
const response = await fetch("http://localhost:8080/auth/kakao/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) throw new Error((await response.json()).message || "ํ์๊ฐ์
์คํจ");
return await response.json();
}
7-2. ํ์๊ฐ์ ํผ ์ปดํฌ๋ํธ (SocialSignupForm.tsx)
import { useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import registerKakaoUser from "@/feature/social-signup/api/register";
import type { KakaoSocialSignUpRequest } from "@/feature/social-signup/types/signup.types";
export default function SocialSignupForm() {
const router = useRouter();
const searchParams = useSearchParams();
// ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ์์ provider, kakaoId, email ๊ฐ์ ธ์ค๊ธฐ
const provider = searchParams.get("provider");
const kakaoId = searchParams.get("kakaoId");
const email = searchParams.get("email");
// ์ํ ๊ด๋ฆฌ
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [termsAgreed, setTermsAgreed] = useState(false);
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
// ํผ ์ ์ถ ํธ๋ค๋ฌ
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
// ํ์ ์
๋ ฅ๊ฐ ์ฒดํฌ
if (!name || !phone || !termsAgreed) {
setError("์ค๋ช
, ํด๋ํฐ๋ฒํธ, ์ฝ๊ด๋์ ๋ชจ๋ ํ์์
๋๋ค.");
return;
}
setLoading(true);
try {
// API ์์ฒญ์ ์ฌ์ฉํ ๋ฐ์ดํฐ
const payload: KakaoSocialSignUpRequest = {
provider: provider ?? "kakao",
kakaoId: kakaoId ?? undefined,
email: email ?? undefined,
name,
phone,
termsAgreed,
};
// ๋ฐฑ์๋๋ก ํ์๊ฐ์
API ํธ์ถ
const res = await registerKakaoUser(payload);
localStorage.setItem("jwt_token", res.token);
router.replace("/dashboard");
} catch (err) {
setError("ํ์๊ฐ์
์ ์คํจํ์ต๋๋ค.");
} finally {
setLoading(false);
}
};
// (์๋๋ UI ์๋ต)
// ...
}
- ์นด์นด์ค ๋ก๊ทธ์ธ ๋ฒํผ: ๋ฐฑ์๋ ์ธ๊ฐ URL๋ก ์ด๋ (์นด์นด์ค ๋ก๊ทธ์ธ ํ์ด์ง)
- ์ฝ๋ฐฑ URL: ํ์์ฌ๋ถ์ ๋ฐ๋ผ ๋์๋ณด๋ or ํ์๊ฐ์ ํผ ๋ฆฌ๋ค์ด๋ ํธ
- ํ์๊ฐ์ ํผ: ์ค๋ช , ํด๋ํฐ, ์ฝ๊ด๋์(ํ์) ์ ๋ ฅ
- ํผ ์ ์ถ: ์ ํจ์ฑ ๊ฒ์ฌ → ๋ฐฑ์๋ API POST → JWT ์ ์ฅ, ๋์๋ณด๋ ์ด๋
์ด๋ฒ ํฌ์คํ ์์๋ ์นด์นด์ค ์์ ๋ก๊ทธ์ธ์ ์ค์ ๋ก ํ๋ก์ ํธ์ ์ ์ฉํ๋ ์ ์ฒด ๊ณผ์ ์ ์์ธ์ ๊ฐ๋จ ๊ทธ ์ฌ์ด ์ด๋ ์ฏค???? ์ผ๋ก ์ ๋ฆฌํด๋ณด์๋ค.
OAuth 2.0 ๊ธฐ๋ณธ ๊ฐ๋ ๋ถํฐ, ์ค์ Spring Boot์ React๋ฅผ ์ฐ๋ํ์ฌ ๋ก๊ทธ์ธ ๋ฐ ํ์๊ฐ์ ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ๊น์ง ๋ง์ ๊ฒฝํ์ ํ ์ ์์๋ ์๊ฐ์ด์๋ค.
์ง์ ๊ตฌํํ๋ฉฐ ์ป์ ๊ฒฝํ์ ํตํด ์ธ์ฆ, API ๋ณด์, ํ๋ก ํธ-๋ฐฑ์๋ ์ฐ๋, ์๋ฌ ์ฒ๋ฆฌ ๋ฑ ์น ์๋น์ค์์ ํ์์ ์ธ ๊ธฐ์ ๋ค์ ๋์ฑ ๊น์ด ์ดํดํ ์ ์์๋ค.
โน๏ธ ์ฐธ๊ณ
[ - ]
'๐ง๐ปโ๐ป Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring Boot] ํด๋ํฐ ๋ฌธ์์ธ์ฆ(SMS) ๊ตฌํํ๊ธฐ (feat.CoolSMS API) (0) | 2025.07.15 |
---|