Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.example.siljeun.domain.auth.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.siljeun.domain.auth.dto.request.LoginRequest;
import org.example.siljeun.domain.auth.dto.request.SignUpRequest;
import org.example.siljeun.domain.auth.dto.response.LoginResponse;
Expand All @@ -17,6 +18,7 @@
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {

private final AuthService authService;
Expand All @@ -33,6 +35,7 @@ public ResponseEntity<SignUpResponse> signUp(@RequestBody SignUpRequest request)
@PostMapping("/login")
public ResponseDto<LoginResponse> login(@RequestBody LoginRequest request) {
try {
log.debug("----- 로그인 메서드 실행 -----");
LoginResponse response = authService.login(request.username(), request.password());
return ResponseDto.success("로그인 성공", response);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public SignUpResponse signUp(SignUpRequest request) {
String password = passwordEncoder.encode(request.password());

// 회원 생성 및 저장
User user = new User(request.email(), request.username(), password, request.nickname(),
request.role(), request.provider());
User user = new User(request.email(), request.username(), password, request.name(),
request.nickname(), request.role(), request.provider());
User savedUser = userRepository.save(user);

return new SignUpResponse(savedUser.getId(), savedUser.getEmail(), savedUser.getUsername());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,73 +1,71 @@
package org.example.siljeun.domain.oauth.client;

import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.siljeun.domain.oauth.dto.KakaoAccessToken;
import org.example.siljeun.domain.oauth.dto.KakaoUserInfo;
import org.example.siljeun.global.config.KakaoOAuthProperties;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Component
@RequiredArgsConstructor
public class KakaoApiClient {
@Slf4j
public class KakaoOAuthClient {

private final RestTemplate restTemplate;
private final KakaoOAuthProperties properties;

// 현재 카카오 API 서버에서 인가 코드를 제공한 상태이다
// 서비스 서버가 인가 코드를 이용해 카카오 API 서버로 액세스 토큰을 요청한다
public String getAccessToken(String code) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
// 아래 4가지 값은 필수
params.add("grant_type", "authorization_code");
params.add("client_id", "eaee0e144aeb9afef54d5c449448baea");
params.add("redirect_uri", "http://localhost:8080/oauth/kakao/callback");
params.add("code", code);
params.add("client_id", "eaee0e144aeb9afef54d5c449448baea"); // 카카오 REST API 키
params.add("redirect_uri", "http://localhost:8080/oauth/kakao/callback"); // 여기서 문제 발생?
params.add("code", code); // 인가 코드

HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
// 명시한 URL로 (인가 코드를 담은) POST 요청을 보내면 카카오 API 서버에서 액세스 토큰을 응답한다
KakaoAccessToken response = restTemplate.postForEntity(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
request,
new ParameterizedTypeReference<>() {
}
);
KakaoAccessToken.class
).getBody();

if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) {
throw new RuntimeException("카카오 Access Token 요청 실패");
}
log.debug("----- 액세스 토큰: {} -----", response.accessToken());

return response.getBody().get("access_token").toString();
return response.accessToken();
}

// 서비스 서버가 카카오 인증 서버에 저장된 회원 정보를 요청한다
public KakaoUserInfo getUserInfo(String accessToken) {
HttpHeaders headers = new HttpHeaders();
// HTTP 헤더 설정
final HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

HttpEntity<Void> request = new HttpEntity<>(headers);
// 설정한 HTTP 헤더를 이용해 요청 생성
final HttpEntity<Void> request = new HttpEntity<>(headers);

ResponseEntity<KakaoUserInfo> response = restTemplate.exchange(
// GET 메서드로 회원 정보를 요청한 후 KakaoUserInfo 객체에 담음
final KakaoUserInfo response = restTemplate.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.GET,
request,
KakaoUserInfo.class
);

if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) {
throw new RuntimeException("카카오 사용자 정보 요청 실패");
}
).getBody();

return response.getBody();
return response;
}

}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.example.siljeun.domain.oauth.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.siljeun.domain.auth.dto.response.LoginResponse;
import org.example.siljeun.domain.oauth.service.KakaoOAuthService;
import org.example.siljeun.global.dto.ResponseDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -12,14 +13,21 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/oauth")
@Slf4j
public class OAuthController {

private final KakaoOAuthService kakaoOAuthService;

/*
1. 클라이언트가 카카오 로그인을 요청한다
2. /oauth/kakao/callback?code={code}로 리다이렉트된다
3. 이때 카카오에서 쿼리 스트링으로 인가 코드를 넘겨준다
4. 넘어온 인가 코드를 이용해서 카카오 로그인 API를 호출한다
*/
@GetMapping("/kakao/callback")
public ResponseEntity<?> kakaoCallback(@RequestParam String code) {
String jwt = kakaoOAuthService.kakaoLogin(code);
return ResponseEntity.ok(jwt);
public ResponseEntity<LoginResponse> kakaoCallback(@RequestParam String code) {
log.debug("---------- METHOD: kakaoCallback ----------");
return ResponseEntity.ok(kakaoOAuthService.kakaoLogin(code));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.example.siljeun.domain.oauth.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record KakaoAccessToken(String tokenType,
String accessToken,
Integer expiresIn,
String refreshToken,
Integer refreshTokenExpiresIn) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.example.siljeun.domain.oauth.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record KakaoAccount(
KakaoProfile profile, // 프로필 정보(닉네임, 프로필 사진)
String email
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.example.siljeun.domain.oauth.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record KakaoProfile(
String nickname,
String profileImageUrl
) {

}
Original file line number Diff line number Diff line change
@@ -1,41 +1,12 @@
package org.example.siljeun.domain.oauth.dto;

import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class KakaoUserInfo implements OAuth2UserInfo {

private Map<String, Object> attributes;
private Map<String, Object> attributesAccount;
private Map<String, Object> attributesProfile;

public KakaoUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
this.attributesAccount = (Map<String, Object>) attributes.get("kakao_account");
this.attributesProfile = (Map<String, Object>) attributesAccount.get("profile");
}

@Override
public String getProvider() {
return "Kakao";
}

@Override
public String getProviderId() {
return attributes.get("id").toString();
}

@Override
public String getEmail() {
return attributesAccount.get("email").toString();
}

@Override
public String getNickname() {
return attributesProfile.get("nickname").toString();
}
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record KakaoUserInfo(
Long id, // 회원 번호
KakaoAccount kakaoAccount // 카카오 계정 정보
) {

}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,40 +1,73 @@
package org.example.siljeun.domain.oauth.service;

import lombok.RequiredArgsConstructor;
import org.example.siljeun.domain.oauth.client.KakaoApiClient;
import lombok.extern.slf4j.Slf4j;
import org.example.siljeun.domain.auth.dto.response.LoginResponse;
import org.example.siljeun.domain.oauth.client.KakaoOAuthClient;
import org.example.siljeun.domain.oauth.dto.KakaoUserInfo;
import org.example.siljeun.domain.user.entity.User;
import org.example.siljeun.domain.user.enums.Provider;
import org.example.siljeun.domain.user.enums.Role;
import org.example.siljeun.domain.user.repository.UserRepository;
import org.example.siljeun.global.security.JwtUtil;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class KakaoOAuthService {

private final KakaoApiClient kakaoApiClient;
private final KakaoOAuthClient kakaoOAuthClient;
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder passwordEncoder;

public String kakaoLogin(String code) {
// 1. 액세스 토큰 요청
String accessToken = kakaoApiClient.getAccessToken(code);
// 인가 코드를 이용해 카카오 로그인 API를 호출한다
public LoginResponse kakaoLogin(String code) {
// 1. 카카오에 인가 코드를 넘겨서 액세스 토큰을 획득한다
log.debug("----- 액세스 토큰 발급 -----");
final String accessToken = kakaoOAuthClient.getAccessToken(code);

// 2. 사용자 정보 요청
KakaoUserInfo userInfo = kakaoApiClient.getUserInfo(accessToken);
// 2. 카카오에 액세스 토큰을 넘겨서 카카오에 저장된 사용자 정보를 획득한다
log.debug("----- 사용자 정보 획득 -----");
final KakaoUserInfo userInfo = kakaoOAuthClient.getUserInfo(accessToken);

// 3. 회원 가입 또는 로그인 처리
User user = userRepository.findByEmail(userInfo.getEmail())
// 3. 해당 정보를 이용해 회원 가입 또는 로그인을 처리한다
log.debug("----- 회원 가입 또는 로그인 -----");
User user = userRepository.findByEmail(userInfo.kakaoAccount().email())
.orElseGet(() -> registerUser(userInfo));

// 4. JWT 토큰 발급
return jwtUtil.createToken(user.getUsername());
// 4. 서비스 서버에 저장된 회원 정보를 이용해 JWT 토큰을 발급받는다
log.debug("----- JWT 토큰 발급 -----");
String token = jwtUtil.createToken(user.getUsername());

return new LoginResponse(token);
}

private User registerUser(KakaoUserInfo userInfo) {
User user = new User(userInfo.getEmail(), userInfo.getNickname(), Provider.KAKAO,
userInfo.getProviderId());
String username = "kakao" + userInfo.id();
String password = passwordEncoder.encode(username);
User user = new User(
userInfo.kakaoAccount().email(),
username,
password,
userInfo.kakaoAccount().profile().nickname(),
userInfo.kakaoAccount().profile().nickname(),
Role.USER,
Provider.KAKAO,
userInfo.id()
);
log.debug("--------------------회원 가입 메서드 실행--------------------");
log.debug("email: {}, username: {}, password: {}, name: {}, nickname: {}, id: {}",
userInfo.kakaoAccount().email(),
username,
password,
userInfo.kakaoAccount().profile().nickname(),
userInfo.kakaoAccount().profile().nickname(),
userInfo.id()
);

return userRepository.save(user);
}

Expand Down

This file was deleted.

Loading