diff --git a/src/main/java/org/example/siljeun/domain/auth/controller/AuthController.java b/src/main/java/org/example/siljeun/domain/auth/controller/AuthController.java index 0ee8150..24d458e 100644 --- a/src/main/java/org/example/siljeun/domain/auth/controller/AuthController.java +++ b/src/main/java/org/example/siljeun/domain/auth/controller/AuthController.java @@ -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; @@ -17,6 +18,7 @@ @RestController @RequestMapping("/auth") @RequiredArgsConstructor +@Slf4j public class AuthController { private final AuthService authService; @@ -33,6 +35,7 @@ public ResponseEntity signUp(@RequestBody SignUpRequest request) @PostMapping("/login") public ResponseDto login(@RequestBody LoginRequest request) { try { + log.debug("----- 로그인 메서드 실행 -----"); LoginResponse response = authService.login(request.username(), request.password()); return ResponseDto.success("로그인 성공", response); } catch (Exception e) { diff --git a/src/main/java/org/example/siljeun/domain/auth/service/AuthService.java b/src/main/java/org/example/siljeun/domain/auth/service/AuthService.java index 98cbc21..e17f34b 100644 --- a/src/main/java/org/example/siljeun/domain/auth/service/AuthService.java +++ b/src/main/java/org/example/siljeun/domain/auth/service/AuthService.java @@ -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()); diff --git a/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java b/src/main/java/org/example/siljeun/domain/oauth/client/KakaoOAuthClient.java similarity index 50% rename from src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java rename to src/main/java/org/example/siljeun/domain/oauth/client/KakaoOAuthClient.java index 3d1f01a..f76628c 100644 --- a/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java +++ b/src/main/java/org/example/siljeun/domain/oauth/client/KakaoOAuthClient.java @@ -1,16 +1,13 @@ 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; @@ -18,56 +15,57 @@ @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 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> request = new HttpEntity<>(params, headers); - ResponseEntity> 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 request = new HttpEntity<>(headers); + // 설정한 HTTP 헤더를 이용해 요청 생성 + final HttpEntity request = new HttpEntity<>(headers); - ResponseEntity 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; } -} +} \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/oauth/client/NaverApiClient.java b/src/main/java/org/example/siljeun/domain/oauth/client/NaverApiClient.java deleted file mode 100644 index 88c1713..0000000 --- a/src/main/java/org/example/siljeun/domain/oauth/client/NaverApiClient.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.example.siljeun.domain.oauth.client; - -public class NaverApiClient { - -} diff --git a/src/main/java/org/example/siljeun/domain/oauth/controller/OAuthController.java b/src/main/java/org/example/siljeun/domain/oauth/controller/OAuthController.java index ffc0e55..4c11678 100644 --- a/src/main/java/org/example/siljeun/domain/oauth/controller/OAuthController.java +++ b/src/main/java/org/example/siljeun/domain/oauth/controller/OAuthController.java @@ -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; @@ -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 kakaoCallback(@RequestParam String code) { + log.debug("---------- METHOD: kakaoCallback ----------"); + return ResponseEntity.ok(kakaoOAuthService.kakaoLogin(code)); } } \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoAccessToken.java b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoAccessToken.java new file mode 100644 index 0000000..ad8ad55 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoAccessToken.java @@ -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) { + +} diff --git a/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoAccount.java b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoAccount.java new file mode 100644 index 0000000..0f271d1 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoAccount.java @@ -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 +) { + +} \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoProfile.java b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoProfile.java new file mode 100644 index 0000000..c85a06c --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoProfile.java @@ -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 +) { + +} \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoUserInfo.java b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoUserInfo.java index f5c855f..f6660c1 100644 --- a/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoUserInfo.java +++ b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoUserInfo.java @@ -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 attributes; - private Map attributesAccount; - private Map attributesProfile; - - public KakaoUserInfo(Map attributes) { - this.attributes = attributes; - this.attributesAccount = (Map) attributes.get("kakao_account"); - this.attributesProfile = (Map) 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 // 카카오 계정 정보 +) { } \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/oauth/dto/NaverUserInfo.java b/src/main/java/org/example/siljeun/domain/oauth/dto/NaverUserInfo.java deleted file mode 100644 index 3aff8c2..0000000 --- a/src/main/java/org/example/siljeun/domain/oauth/dto/NaverUserInfo.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.example.siljeun.domain.oauth.dto; - -public class NaverUserInfo { - -} diff --git a/src/main/java/org/example/siljeun/domain/oauth/dto/OAuth2UserInfo.java b/src/main/java/org/example/siljeun/domain/oauth/dto/OAuth2UserInfo.java deleted file mode 100644 index e47bd09..0000000 --- a/src/main/java/org/example/siljeun/domain/oauth/dto/OAuth2UserInfo.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.example.siljeun.domain.oauth.dto; - -import java.util.Map; - -public interface OAuth2UserInfo { - - public Map getAttributes(); - - String getProvider(); - - String getProviderId(); - - String getEmail(); - - String getNickname(); - -} \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/oauth/service/KakaoOAuthService.java b/src/main/java/org/example/siljeun/domain/oauth/service/KakaoOAuthService.java index 8776f1c..cbd4cb0 100644 --- a/src/main/java/org/example/siljeun/domain/oauth/service/KakaoOAuthService.java +++ b/src/main/java/org/example/siljeun/domain/oauth/service/KakaoOAuthService.java @@ -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); } diff --git a/src/main/java/org/example/siljeun/domain/oauth/service/NaverOAuthService.java b/src/main/java/org/example/siljeun/domain/oauth/service/NaverOAuthService.java deleted file mode 100644 index 0fec45e..0000000 --- a/src/main/java/org/example/siljeun/domain/oauth/service/NaverOAuthService.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.example.siljeun.domain.oauth.service; - -import org.springframework.stereotype.Service; - -@Service -public class NaverOAuthService { - -} \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/user/entity/User.java b/src/main/java/org/example/siljeun/domain/user/entity/User.java index 14ec94f..575d8c1 100644 --- a/src/main/java/org/example/siljeun/domain/user/entity/User.java +++ b/src/main/java/org/example/siljeun/domain/user/entity/User.java @@ -51,23 +51,31 @@ public class User extends BaseEntity { @Column(nullable = false) private Provider provider; - private String providerId; + private Long providerId; private LocalDateTime deletedAt; - public User(String email, String username, String password, String nickname, Role role, - Provider provider) { + // 로컬 회원 가입용 생성자 + public User(String email, String username, String password, String name, String nickname, + Role role, Provider provider) { this.email = email; this.username = username; this.password = password; + this.name = name; this.nickname = nickname; this.role = role; this.provider = provider; } - public User(String email, String nickname, Provider provider, String providerId) { + // 소셜 회원 가입용 생성자 + public User(String email, String username, String password, String name, String nickname, + Role role, Provider provider, Long providerId) { this.email = email; + this.username = username; + this.password = password; + this.name = name; this.nickname = nickname; + this.role = role; this.provider = provider; this.providerId = providerId; } diff --git a/src/main/java/org/example/siljeun/global/config/SecurityConfig.java b/src/main/java/org/example/siljeun/global/config/SecurityConfig.java index 9afd186..70a80e3 100644 --- a/src/main/java/org/example/siljeun/global/config/SecurityConfig.java +++ b/src/main/java/org/example/siljeun/global/config/SecurityConfig.java @@ -31,14 +31,17 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/auth/**", "/oauth2/**", "/login/**", "/ws/**", "/ws","/checkout.html","/payments","/success.html").permitAll() + .requestMatchers("/auth/**", "/oauth/**", "/oauth2/**", "/login/**", "/ws/**", "/ws", + "/checkout.html", "/payments", "/success.html").permitAll() .anyRequest().authenticated() ) -// .oauth2Login(oauth2 -> oauth2 -// .successHandler(customOAuth2SuccessHandler) -// .defaultSuccessUrl("/auth/oauth2/success", true) -// .failureUrl("/auth/oauth2/failure") -// ) + .oauth2Login(oauth2 -> oauth2 + .successHandler(customOAuth2SuccessHandler) + .defaultSuccessUrl("/auth/oauth2/success") + .failureUrl("/auth/oauth2/failure") + ) +// .formLogin(form -> form +// .loginPage("/login")) .addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class); diff --git a/src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java b/src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java index dff18e0..8a32da3 100644 --- a/src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java +++ b/src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; @@ -14,6 +15,7 @@ @Component @RequiredArgsConstructor +@Slf4j public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler { private final JwtUtil jwtUtil; @@ -21,6 +23,8 @@ public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + log.debug("----- 로그인 성공 -----"); + // principal에서 사용자 정보 추출 OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); String username = "kakao_" + oAuth2User.getAttribute("id").toString(); @@ -41,6 +45,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write("{\"message\": \"Login successful\"}"); response.getWriter().flush(); + + // 기본 경로로 리다이렉트 + response.sendRedirect("/"); } } diff --git a/src/main/java/org/example/siljeun/global/security/PrincipalDetails.java b/src/main/java/org/example/siljeun/global/security/PrincipalDetails.java index 40c0d2a..dc7e71c 100644 --- a/src/main/java/org/example/siljeun/global/security/PrincipalDetails.java +++ b/src/main/java/org/example/siljeun/global/security/PrincipalDetails.java @@ -2,12 +2,14 @@ import java.util.Collection; import java.util.List; +import lombok.Getter; import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.user.entity.User; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +@Getter @RequiredArgsConstructor public class PrincipalDetails implements UserDetails { @@ -49,7 +51,8 @@ public boolean isCredentialsNonExpired() { @Override public boolean isEnabled() { - return user.getDeletedAt() != null; + // 삭제되지 않은 계정은 모두 활성화된 것으로 취급 + return user.getDeletedAt() == null; } -} +} \ No newline at end of file