From 68da97fda7e17f5885d2066fe0de035147c29f16 Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Wed, 21 May 2025 14:26:57 +0900 Subject: [PATCH 1/8] =?UTF-8?q?build:=20.gitignore=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c2065bc..03c8c58 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +src/main/resources/application.yml ### STS ### .apt_generated From b15daca6c867ff71cac7d85dd09aac76659502f3 Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Wed, 21 May 2025 16:59:32 +0900 Subject: [PATCH 2/8] =?UTF-8?q?build:=20OAuth2=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 036b2ad..a73754c 100644 --- a/build.gradle +++ b/build.gradle @@ -36,9 +36,11 @@ dependencies { // auth implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + } tasks.named('test') { From 41c3467b204822ea03b9fc423ea7e90ee610a79a Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Wed, 21 May 2025 16:59:54 +0900 Subject: [PATCH 3/8] =?UTF-8?q?style:=20DTO=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/example/siljeun/domain/auth/dto/LoginRequestDto.java | 5 ----- .../auth/dto/{LoginResponseDto.java => LoginResponse.java} | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 src/main/java/org/example/siljeun/domain/auth/dto/LoginRequestDto.java rename src/main/java/org/example/siljeun/domain/auth/dto/{LoginResponseDto.java => LoginResponse.java} (81%) diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequestDto.java b/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequestDto.java deleted file mode 100644 index 448ba4b..0000000 --- a/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequestDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.example.siljeun.domain.auth.dto; - -public record LoginRequestDto(String username, String password) { - -} \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/LoginResponseDto.java b/src/main/java/org/example/siljeun/domain/auth/dto/LoginResponse.java similarity index 81% rename from src/main/java/org/example/siljeun/domain/auth/dto/LoginResponseDto.java rename to src/main/java/org/example/siljeun/domain/auth/dto/LoginResponse.java index 1990ae2..640e04d 100644 --- a/src/main/java/org/example/siljeun/domain/auth/dto/LoginResponseDto.java +++ b/src/main/java/org/example/siljeun/domain/auth/dto/LoginResponse.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public class LoginResponseDto { +public class LoginResponse { private final String token; From ff1cf7deff349d8f6f7949c9e82e3cf596814358 Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Wed, 21 May 2025 17:00:07 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 8 +- .../siljeun/domain/auth/dto/LoginRequest.java | 5 ++ .../domain/auth/service/AuthService.java | 9 +-- .../domain/oauth/client/KakaoApiClient.java | 73 +++++++++++++++++++ .../oauth/controller/OAuthController.java | 25 +++++++ .../domain/oauth/dto/KakaoUserInfo.java | 14 ++++ .../oauth/service/KakaoOAuthService.java | 41 +++++++++++ .../siljeun/domain/user/entity/User.java | 16 +++- .../user/repository/UserRepository.java | 3 + .../global/config/KakaoOAuthProperties.java | 19 +++++ .../global/config/RestTemplateConfig.java | 15 ++++ .../siljeun/global/config/SecurityConfig.java | 14 +++- .../security/CustomOAuth2SuccessHandler.java | 40 ++++++++++ .../JwtAuthenticationFilter.java | 2 +- .../global/{jwt => security}/JwtUtil.java | 2 +- src/main/resources/application.properties | 1 - 16 files changed, 269 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/example/siljeun/domain/auth/dto/LoginRequest.java create mode 100644 src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java create mode 100644 src/main/java/org/example/siljeun/domain/oauth/controller/OAuthController.java create mode 100644 src/main/java/org/example/siljeun/domain/oauth/dto/KakaoUserInfo.java create mode 100644 src/main/java/org/example/siljeun/domain/oauth/service/KakaoOAuthService.java create mode 100644 src/main/java/org/example/siljeun/global/config/KakaoOAuthProperties.java create mode 100644 src/main/java/org/example/siljeun/global/config/RestTemplateConfig.java create mode 100644 src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java rename src/main/java/org/example/siljeun/global/{jwt => security}/JwtAuthenticationFilter.java (98%) rename src/main/java/org/example/siljeun/global/{jwt => security}/JwtUtil.java (97%) delete mode 100644 src/main/resources/application.properties 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 b4acd09..e44d655 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,8 +1,8 @@ package org.example.siljeun.domain.auth.controller; import lombok.RequiredArgsConstructor; -import org.example.siljeun.domain.auth.dto.LoginRequestDto; -import org.example.siljeun.domain.auth.dto.LoginResponseDto; +import org.example.siljeun.domain.auth.dto.LoginRequest; +import org.example.siljeun.domain.auth.dto.LoginResponse; import org.example.siljeun.domain.auth.service.AuthService; import org.example.siljeun.global.dto.ResponseDto; import org.springframework.stereotype.Controller; @@ -18,9 +18,9 @@ public class AuthController { private final AuthService authService; @PostMapping("/login") - public ResponseDto login(@RequestBody LoginRequestDto request) { + public ResponseDto login(@RequestBody LoginRequest request) { try { - LoginResponseDto response = authService.login(request.username(), request.password()); + LoginResponse response = authService.login(request.username(), request.password()); return ResponseDto.success("로그인 성공", response); } catch (Exception e) { return ResponseDto.fail("아이디 또는 비밀번호가 올바르지 않습니다."); diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequest.java b/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequest.java new file mode 100644 index 0000000..7a12f6a --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequest.java @@ -0,0 +1,5 @@ +package org.example.siljeun.domain.auth.dto; + +public record LoginRequest(String username, String password) { + +} \ No newline at end of file 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 0c2cb48..a056f66 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 @@ -1,9 +1,8 @@ package org.example.siljeun.domain.auth.service; import lombok.RequiredArgsConstructor; -import org.example.siljeun.domain.auth.dto.LoginResponseDto; -import org.example.siljeun.global.dto.ResponseDto; -import org.example.siljeun.global.jwt.JwtUtil; +import org.example.siljeun.domain.auth.dto.LoginResponse; +import org.example.siljeun.global.security.JwtUtil; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; @@ -17,14 +16,14 @@ public class AuthService { private final AuthenticationManagerBuilder authManagerBuilder; private final JwtUtil jwtUtil; - public LoginResponseDto login(String username, String password) { + public LoginResponse login(String username, String password) { Authentication authentication = authManagerBuilder.getObject() .authenticate(new UsernamePasswordAuthenticationToken(username, password)); SecurityContextHolder.getContext().setAuthentication(authentication); String token = jwtUtil.createToken(username); - return new LoginResponseDto(token); + return new LoginResponse(token); } } \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java b/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java new file mode 100644 index 0000000..48f2107 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java @@ -0,0 +1,73 @@ +package org.example.siljeun.domain.oauth.client; + +import java.util.Map; +import lombok.RequiredArgsConstructor; +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 { + + private final RestTemplate restTemplate; + private final KakaoOAuthProperties properties; + + public String getAccessToken(String code) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", properties.getClientId()); + params.add("redirect_uri", properties.getRedirectUri()); + params.add("code", code); + + HttpEntity> request = new HttpEntity<>(params, headers); + + ResponseEntity> response = restTemplate.exchange( + properties.getTokenUri(), + HttpMethod.POST, + request, + new ParameterizedTypeReference<>() { + } + ); + + if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) { + throw new RuntimeException("카카오 Access Token 요청 실패"); + } + + return response.getBody().get("access_token").toString(); + } + + public KakaoUserInfo getUserInfo(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + + HttpEntity request = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + properties.getUserInfoUri(), + HttpMethod.GET, + request, + KakaoUserInfo.class + ); + + if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) { + throw new RuntimeException("카카오 사용자 정보 요청 실패"); + } + + return response.getBody(); + } + +} 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 new file mode 100644 index 0000000..ffc0e55 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/controller/OAuthController.java @@ -0,0 +1,25 @@ +package org.example.siljeun.domain.oauth.controller; + +import lombok.RequiredArgsConstructor; +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; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/oauth") +public class OAuthController { + + private final KakaoOAuthService kakaoOAuthService; + + @GetMapping("/kakao/callback") + public ResponseEntity kakaoCallback(@RequestParam String code) { + String jwt = kakaoOAuthService.kakaoLogin(code); + return ResponseEntity.ok(jwt); + } + +} \ 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 new file mode 100644 index 0000000..c1afefe --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/dto/KakaoUserInfo.java @@ -0,0 +1,14 @@ +package org.example.siljeun.domain.oauth.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class KakaoUserInfo { + + private Long id; + private String email; + private String nickname; + +} \ 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 new file mode 100644 index 0000000..ffe393d --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/service/KakaoOAuthService.java @@ -0,0 +1,41 @@ +package org.example.siljeun.domain.oauth.service; + +import lombok.RequiredArgsConstructor; +import org.example.siljeun.domain.oauth.client.KakaoApiClient; +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.repository.UserRepository; +import org.example.siljeun.global.security.JwtUtil; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class KakaoOAuthService { + + private final KakaoApiClient kakaoApiClient; + private final UserRepository userRepository; + private final JwtUtil jwtUtil; + + public String kakaoLogin(String code) { + // 1. 액세스 토큰 요청 + String accessToken = kakaoApiClient.getAccessToken(code); + + // 2. 사용자 정보 요청 + KakaoUserInfo userInfo = kakaoApiClient.getUserInfo(accessToken); + + // 3. 회원 가입 또는 로그인 처리 + User user = userRepository.findByEmail(userInfo.getEmail()) + .orElseGet(() -> registerUser(userInfo)); + + // 4. JWT 토큰 발급 + return jwtUtil.createToken(user.getUsername()); + } + + private User registerUser(KakaoUserInfo userInfo) { + User user = new User(userInfo.getEmail(), userInfo.getNickname(), Provider.KAKAO, + userInfo.getId()); + return userRepository.save(user); + } + +} \ 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 ac05501..bf4d338 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 @@ -9,6 +9,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import java.time.LocalDateTime; +import lombok.Builder; import lombok.Getter; import org.example.siljeun.domain.user.enums.Provider; import org.example.siljeun.domain.user.enums.Role; @@ -32,7 +33,10 @@ public class User extends BaseEntity { @Column(nullable = false, length = 255) private String password; - @Column(nullable = false, length = 255) + @Column(nullable = false, length = 10) + private String nickname; + + @Column(length = 255) private String address; @Enumerated(EnumType.STRING) @@ -43,9 +47,15 @@ public class User extends BaseEntity { @Column(nullable = false) private Provider provider; - @Column(name = "provider_id", length = 255) - private String providerId; + private Long providerId; private LocalDateTime deletedAt; + public User(String email, String nickname, Provider provider, Long providerId) { + this.email = email; + this.nickname = nickname; + this.provider = provider; + this.providerId = providerId; + } + } \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/user/repository/UserRepository.java b/src/main/java/org/example/siljeun/domain/user/repository/UserRepository.java index bb14960..6a5ec64 100644 --- a/src/main/java/org/example/siljeun/domain/user/repository/UserRepository.java +++ b/src/main/java/org/example/siljeun/domain/user/repository/UserRepository.java @@ -7,4 +7,7 @@ public interface UserRepository extends JpaRepository { Optional findByUsername(String username); + + Optional findByEmail(String email); + } \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/global/config/KakaoOAuthProperties.java b/src/main/java/org/example/siljeun/global/config/KakaoOAuthProperties.java new file mode 100644 index 0000000..54a47f4 --- /dev/null +++ b/src/main/java/org/example/siljeun/global/config/KakaoOAuthProperties.java @@ -0,0 +1,19 @@ +package org.example.siljeun.global.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "kakao") +public class KakaoOAuthProperties { + + private String clientId; + private String redirectUri; + private String tokenUri; + private String userInfoUri; + +} diff --git a/src/main/java/org/example/siljeun/global/config/RestTemplateConfig.java b/src/main/java/org/example/siljeun/global/config/RestTemplateConfig.java new file mode 100644 index 0000000..d8c7359 --- /dev/null +++ b/src/main/java/org/example/siljeun/global/config/RestTemplateConfig.java @@ -0,0 +1,15 @@ +package org.example.siljeun.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +} 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 5068c7a..8976d6a 100644 --- a/src/main/java/org/example/siljeun/global/config/SecurityConfig.java +++ b/src/main/java/org/example/siljeun/global/config/SecurityConfig.java @@ -2,8 +2,9 @@ import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.user.service.CustomUserDetailsService; -import org.example.siljeun.global.jwt.JwtAuthenticationFilter; -import org.example.siljeun.global.jwt.JwtUtil; +import org.example.siljeun.global.security.CustomOAuth2SuccessHandler; +import org.example.siljeun.global.security.JwtAuthenticationFilter; +import org.example.siljeun.global.security.JwtUtil; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -19,17 +20,24 @@ public class SecurityConfig { private final JwtUtil jwtUtil; private final CustomUserDetailsService userDetailsService; + private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) + .formLogin(form -> form.disable()) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/auth/**", "/oauth2/**", "/login/**").permitAll() .anyRequest().authenticated() ) + .oauth2Login(oauth2 -> oauth2 + .successHandler(customOAuth2SuccessHandler) + .defaultSuccessUrl("/auth/oauth2/success", true) + .failureUrl("/auth/oauth2/failure") + ) .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 new file mode 100644 index 0000000..f4f70c2 --- /dev/null +++ b/src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java @@ -0,0 +1,40 @@ +package org.example.siljeun.global.security; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.security.oauth2.core.user.OAuth2User; + + +@Component +@RequiredArgsConstructor +public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler { + + private final JwtUtil jwtUtil; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + // 1. principal에서 사용자 정보 추출 + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + String username = "kakao_" + oAuth2User.getAttribute("id").toString(); + + // 2. JWT 생성 + String token = jwtUtil.createToken(username); + + // 3. JSON 응답 세팅 + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json;charset=UTF-8"); + + // 4. JWT를 JSON 응답 바디로 내려주기 + String json = String.format("{\"token\":\"%s\"}", token); + response.getWriter().write(json); + response.getWriter().flush(); + } + +} diff --git a/src/main/java/org/example/siljeun/global/jwt/JwtAuthenticationFilter.java b/src/main/java/org/example/siljeun/global/security/JwtAuthenticationFilter.java similarity index 98% rename from src/main/java/org/example/siljeun/global/jwt/JwtAuthenticationFilter.java rename to src/main/java/org/example/siljeun/global/security/JwtAuthenticationFilter.java index 31439ce..e8334a3 100644 --- a/src/main/java/org/example/siljeun/global/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/org/example/siljeun/global/security/JwtAuthenticationFilter.java @@ -1,4 +1,4 @@ -package org.example.siljeun.global.jwt; +package org.example.siljeun.global.security; import jakarta.annotation.Nonnull; import jakarta.servlet.FilterChain; diff --git a/src/main/java/org/example/siljeun/global/jwt/JwtUtil.java b/src/main/java/org/example/siljeun/global/security/JwtUtil.java similarity index 97% rename from src/main/java/org/example/siljeun/global/jwt/JwtUtil.java rename to src/main/java/org/example/siljeun/global/security/JwtUtil.java index adad086..fe6090f 100644 --- a/src/main/java/org/example/siljeun/global/jwt/JwtUtil.java +++ b/src/main/java/org/example/siljeun/global/security/JwtUtil.java @@ -1,4 +1,4 @@ -package org.example.siljeun.global.jwt; +package org.example.siljeun.global.security; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 272c2a3..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=siljeun From 20ef6efb486e6c20f8616b2a0b358b852b23995f Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Thu, 22 May 2025 14:29:31 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EB=84=A4=EC=9D=B4=EB=B2=84=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EC=9E=A1?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/oauth/client/NaverApiClient.java | 5 +++++ .../domain/oauth/dto/NaverUserInfo.java | 5 +++++ .../oauth/service/NaverOAuthService.java | 8 ++++++++ .../global/config/NaverOAuthProperties.java | 20 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/main/java/org/example/siljeun/domain/oauth/client/NaverApiClient.java create mode 100644 src/main/java/org/example/siljeun/domain/oauth/dto/NaverUserInfo.java create mode 100644 src/main/java/org/example/siljeun/domain/oauth/service/NaverOAuthService.java create mode 100644 src/main/java/org/example/siljeun/global/config/NaverOAuthProperties.java 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 new file mode 100644 index 0000000..88c1713 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/client/NaverApiClient.java @@ -0,0 +1,5 @@ +package org.example.siljeun.domain.oauth.client; + +public class NaverApiClient { + +} 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 new file mode 100644 index 0000000..3aff8c2 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/dto/NaverUserInfo.java @@ -0,0 +1,5 @@ +package org.example.siljeun.domain.oauth.dto; + +public class NaverUserInfo { + +} 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 new file mode 100644 index 0000000..0fec45e --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/service/NaverOAuthService.java @@ -0,0 +1,8 @@ +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/global/config/NaverOAuthProperties.java b/src/main/java/org/example/siljeun/global/config/NaverOAuthProperties.java new file mode 100644 index 0000000..2ca3fbc --- /dev/null +++ b/src/main/java/org/example/siljeun/global/config/NaverOAuthProperties.java @@ -0,0 +1,20 @@ +package org.example.siljeun.global.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "naver") +public class NaverOAuthProperties { + + private String clientId; + private String clientSecret; + private String redirectUri; + private String tokenUri; + private String userInfoUri; + +} \ No newline at end of file From 476ab2590561d191a4701c2d97a3f9fc5e92b190 Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Thu, 22 May 2025 19:22:16 +0900 Subject: [PATCH 6/8] =?UTF-8?q?chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../siljeun/domain/auth/dto/{ => request}/LoginRequest.java | 2 +- .../siljeun/domain/auth/dto/{ => response}/LoginResponse.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/org/example/siljeun/domain/auth/dto/{ => request}/LoginRequest.java (55%) rename src/main/java/org/example/siljeun/domain/auth/dto/{ => response}/LoginResponse.java (70%) diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequest.java b/src/main/java/org/example/siljeun/domain/auth/dto/request/LoginRequest.java similarity index 55% rename from src/main/java/org/example/siljeun/domain/auth/dto/LoginRequest.java rename to src/main/java/org/example/siljeun/domain/auth/dto/request/LoginRequest.java index 7a12f6a..f8fe48c 100644 --- a/src/main/java/org/example/siljeun/domain/auth/dto/LoginRequest.java +++ b/src/main/java/org/example/siljeun/domain/auth/dto/request/LoginRequest.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.auth.dto; +package org.example.siljeun.domain.auth.dto.request; public record LoginRequest(String username, String password) { diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/LoginResponse.java b/src/main/java/org/example/siljeun/domain/auth/dto/response/LoginResponse.java similarity index 70% rename from src/main/java/org/example/siljeun/domain/auth/dto/LoginResponse.java rename to src/main/java/org/example/siljeun/domain/auth/dto/response/LoginResponse.java index 640e04d..ec024aa 100644 --- a/src/main/java/org/example/siljeun/domain/auth/dto/LoginResponse.java +++ b/src/main/java/org/example/siljeun/domain/auth/dto/response/LoginResponse.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.auth.dto; +package org.example.siljeun.domain.auth.dto.response; import lombok.RequiredArgsConstructor; From 2a8a4a78113a6ab2ff6cdf4c8635ba3f77878a0a Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Thu, 22 May 2025 19:22:39 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 21 +++++++++++--- .../auth/dto/request/SignUpRequest.java | 5 ++++ .../auth/dto/response/SignUpResponse.java | 14 ++++++++++ .../domain/auth/service/AuthService.java | 28 ++++++++++++------- .../siljeun/domain/user/entity/User.java | 8 ++++++ 5 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java create mode 100644 src/main/java/org/example/siljeun/domain/auth/dto/response/SignUpResponse.java 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 e44d655..0ee8150 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,22 +1,35 @@ package org.example.siljeun.domain.auth.controller; import lombok.RequiredArgsConstructor; -import org.example.siljeun.domain.auth.dto.LoginRequest; -import org.example.siljeun.domain.auth.dto.LoginResponse; +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; +import org.example.siljeun.domain.auth.dto.response.SignUpResponse; import org.example.siljeun.domain.auth.service.AuthService; import org.example.siljeun.global.dto.ResponseDto; -import org.springframework.stereotype.Controller; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; -@Controller +@RestController @RequestMapping("/auth") @RequiredArgsConstructor public class AuthController { private final AuthService authService; + @PostMapping("/signup") + public ResponseEntity signUp(@RequestBody SignUpRequest request) { + SignUpResponse response = authService.signUp(request); + if (response != null) { + return ResponseEntity.ok(response); + } + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); + } + @PostMapping("/login") public ResponseDto login(@RequestBody LoginRequest request) { try { diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java b/src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java new file mode 100644 index 0000000..e4ef7fd --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java @@ -0,0 +1,5 @@ +package org.example.siljeun.domain.auth.dto.request; + +public record SignUpRequest(String email, String username, String password) { + +} \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/response/SignUpResponse.java b/src/main/java/org/example/siljeun/domain/auth/dto/response/SignUpResponse.java new file mode 100644 index 0000000..7d354ec --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/auth/dto/response/SignUpResponse.java @@ -0,0 +1,14 @@ +package org.example.siljeun.domain.auth.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SignUpResponse { + + private Long id; + private String email; + private String username; + +} 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 a056f66..9f02f1c 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 @@ -1,26 +1,34 @@ package org.example.siljeun.domain.auth.service; import lombok.RequiredArgsConstructor; -import org.example.siljeun.domain.auth.dto.LoginResponse; +import org.example.siljeun.domain.auth.dto.request.SignUpRequest; +import org.example.siljeun.domain.auth.dto.response.LoginResponse; +import org.example.siljeun.domain.auth.dto.response.SignUpResponse; +import org.example.siljeun.domain.user.entity.User; +import org.example.siljeun.domain.user.repository.UserRepository; import org.example.siljeun.global.security.JwtUtil; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class AuthService { - private final AuthenticationManagerBuilder authManagerBuilder; private final JwtUtil jwtUtil; + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; - public LoginResponse login(String username, String password) { - Authentication authentication = authManagerBuilder.getObject() - .authenticate(new UsernamePasswordAuthenticationToken(username, password)); + public SignUpResponse signUp(SignUpRequest request) { + // 비밀번호 암호화 + String password = passwordEncoder.encode(request.password()); + + User user = new User(request.email(), request.username(), password); + User savedUser = userRepository.save(user); - SecurityContextHolder.getContext().setAuthentication(authentication); + return new SignUpResponse(savedUser.getId(), savedUser.getEmail(), savedUser.getUsername()); + } + + public LoginResponse login(String username, String password) { String token = jwtUtil.createToken(username); return new LoginResponse(token); 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 bf4d338..5994174 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 @@ -11,6 +11,7 @@ import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.example.siljeun.domain.user.enums.Provider; import org.example.siljeun.domain.user.enums.Role; import org.example.siljeun.global.entity.BaseEntity; @@ -18,6 +19,7 @@ @Getter @Entity @Table(name = "users") +@NoArgsConstructor public class User extends BaseEntity { @Id @@ -51,6 +53,12 @@ public class User extends BaseEntity { private LocalDateTime deletedAt; + public User(String email, String username, String password) { + this.email = email; + this.username = username; + this.password = password; + } + public User(String email, String nickname, Provider provider, Long providerId) { this.email = email; this.nickname = nickname; From 1e5017a5065127699a17e2b710aacafabfb16956 Mon Sep 17 00:00:00 2001 From: jiyun-im-dev Date: Thu, 22 May 2025 20:02:39 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=EB=A1=9C=EC=BB=AC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/dto/request/SignUpRequest.java | 10 +++++- .../auth/dto/response/LoginResponse.java | 4 +++ .../domain/auth/service/AuthService.java | 3 +- .../concert/controller/ConcertController.java | 4 +-- .../domain/oauth/client/KakaoApiClient.java | 8 ++--- .../domain/oauth/dto/KakaoUserInfo.java | 35 ++++++++++++++++--- .../domain/oauth/dto/OAuth2UserInfo.java | 17 +++++++++ .../oauth/service/KakaoOAuthService.java | 2 +- .../user/controller/UserController.java | 4 ++- .../siljeun/domain/user/entity/User.java | 11 +++--- ...vice.java => PrincipalDetailsService.java} | 9 +++-- .../siljeun/global/config/SecurityConfig.java | 19 ++++++---- .../security/CustomOAuth2SuccessHandler.java | 22 +++++++----- .../security/JwtAuthenticationFilter.java | 29 ++++++++------- ...UserDetails.java => PrincipalDetails.java} | 4 +-- 15 files changed, 129 insertions(+), 52 deletions(-) create mode 100644 src/main/java/org/example/siljeun/domain/oauth/dto/OAuth2UserInfo.java rename src/main/java/org/example/siljeun/domain/user/service/{CustomUserDetailsService.java => PrincipalDetailsService.java} (73%) rename src/main/java/org/example/siljeun/global/security/{CustomUserDetails.java => PrincipalDetails.java} (92%) diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java b/src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java index e4ef7fd..b2008b0 100644 --- a/src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java +++ b/src/main/java/org/example/siljeun/domain/auth/dto/request/SignUpRequest.java @@ -1,5 +1,13 @@ package org.example.siljeun.domain.auth.dto.request; -public record SignUpRequest(String email, String username, String password) { +import org.example.siljeun.domain.user.enums.Provider; +import org.example.siljeun.domain.user.enums.Role; + +public record SignUpRequest(String email, + String username, + String password, + String nickname, + Role role, + Provider provider) { } \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/domain/auth/dto/response/LoginResponse.java b/src/main/java/org/example/siljeun/domain/auth/dto/response/LoginResponse.java index ec024aa..eb6ca2d 100644 --- a/src/main/java/org/example/siljeun/domain/auth/dto/response/LoginResponse.java +++ b/src/main/java/org/example/siljeun/domain/auth/dto/response/LoginResponse.java @@ -1,7 +1,11 @@ package org.example.siljeun.domain.auth.dto.response; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.Setter; +@Getter +@Setter @RequiredArgsConstructor public class LoginResponse { 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 9f02f1c..06e35fc 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 @@ -22,7 +22,8 @@ public SignUpResponse signUp(SignUpRequest request) { // 비밀번호 암호화 String password = passwordEncoder.encode(request.password()); - User user = new User(request.email(), request.username(), password); + User user = new User(request.email(), request.username(), password, 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/concert/controller/ConcertController.java b/src/main/java/org/example/siljeun/domain/concert/controller/ConcertController.java index 3e42b96..8af5d13 100644 --- a/src/main/java/org/example/siljeun/domain/concert/controller/ConcertController.java +++ b/src/main/java/org/example/siljeun/domain/concert/controller/ConcertController.java @@ -9,7 +9,7 @@ import org.example.siljeun.domain.concert.dto.response.ConcertDetailResponse; import org.example.siljeun.domain.concert.dto.response.ConcertSimpleResponse; import org.example.siljeun.domain.concert.service.ConcertService; -import org.example.siljeun.global.security.CustomUserDetails; +import org.example.siljeun.global.security.PrincipalDetails; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -31,7 +31,7 @@ public class ConcertController { @PostMapping public ResponseEntity createConcert( @RequestBody @Valid ConcertCreateRequest request, - @AuthenticationPrincipal CustomUserDetails userDetails + @AuthenticationPrincipal PrincipalDetails userDetails ) { Long concertId = concertService.createConcert(request, userDetails.getUserId()); return ResponseEntity.created(URI.create("/concerts" + concertId)).body(concertId); diff --git a/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java b/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java index 48f2107..3d1f01a 100644 --- a/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java +++ b/src/main/java/org/example/siljeun/domain/oauth/client/KakaoApiClient.java @@ -29,14 +29,14 @@ public String getAccessToken(String code) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", "authorization_code"); - params.add("client_id", properties.getClientId()); - params.add("redirect_uri", properties.getRedirectUri()); + params.add("client_id", "eaee0e144aeb9afef54d5c449448baea"); + params.add("redirect_uri", "http://localhost:8080/oauth/kakao/callback"); params.add("code", code); HttpEntity> request = new HttpEntity<>(params, headers); ResponseEntity> response = restTemplate.exchange( - properties.getTokenUri(), + "https://kauth.kakao.com/oauth/token", HttpMethod.POST, request, new ParameterizedTypeReference<>() { @@ -57,7 +57,7 @@ public KakaoUserInfo getUserInfo(String accessToken) { HttpEntity request = new HttpEntity<>(headers); ResponseEntity response = restTemplate.exchange( - properties.getUserInfoUri(), + "https://kapi.kakao.com/v2/user/me", HttpMethod.GET, request, KakaoUserInfo.class 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 c1afefe..f5c855f 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,14 +1,41 @@ package org.example.siljeun.domain.oauth.dto; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor -public class KakaoUserInfo { +public class KakaoUserInfo implements OAuth2UserInfo { - private Long id; - private String email; - private String nickname; + 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(); + } } \ No newline at end of file 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 new file mode 100644 index 0000000..e47bd09 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/oauth/dto/OAuth2UserInfo.java @@ -0,0 +1,17 @@ +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 ffe393d..8776f1c 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 @@ -34,7 +34,7 @@ public String kakaoLogin(String code) { private User registerUser(KakaoUserInfo userInfo) { User user = new User(userInfo.getEmail(), userInfo.getNickname(), Provider.KAKAO, - userInfo.getId()); + userInfo.getProviderId()); return userRepository.save(user); } diff --git a/src/main/java/org/example/siljeun/domain/user/controller/UserController.java b/src/main/java/org/example/siljeun/domain/user/controller/UserController.java index 80e8b5b..4e40c31 100644 --- a/src/main/java/org/example/siljeun/domain/user/controller/UserController.java +++ b/src/main/java/org/example/siljeun/domain/user/controller/UserController.java @@ -5,4 +5,6 @@ @Controller public class UserController { -} + // TODO: 마이 페이지, 회원 정보 수정 등 + +} \ 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 5994174..ddb0653 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 @@ -9,7 +9,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import java.time.LocalDateTime; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.example.siljeun.domain.user.enums.Provider; @@ -49,17 +48,21 @@ public class User extends BaseEntity { @Column(nullable = false) private Provider provider; - private Long providerId; + private String providerId; private LocalDateTime deletedAt; - public User(String email, String username, String password) { + public User(String email, String username, String password, String nickname, Role role, + Provider provider) { this.email = email; this.username = username; this.password = password; + this.nickname = nickname; + this.role = role; + this.provider = provider; } - public User(String email, String nickname, Provider provider, Long providerId) { + public User(String email, String nickname, Provider provider, String providerId) { this.email = email; this.nickname = nickname; this.provider = provider; diff --git a/src/main/java/org/example/siljeun/domain/user/service/CustomUserDetailsService.java b/src/main/java/org/example/siljeun/domain/user/service/PrincipalDetailsService.java similarity index 73% rename from src/main/java/org/example/siljeun/domain/user/service/CustomUserDetailsService.java rename to src/main/java/org/example/siljeun/domain/user/service/PrincipalDetailsService.java index 702ae42..5d81400 100644 --- a/src/main/java/org/example/siljeun/domain/user/service/CustomUserDetailsService.java +++ b/src/main/java/org/example/siljeun/domain/user/service/PrincipalDetailsService.java @@ -1,9 +1,9 @@ package org.example.siljeun.domain.user.service; -import java.util.List; import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.user.entity.User; import org.example.siljeun.domain.user.repository.UserRepository; +import org.example.siljeun.global.security.PrincipalDetails; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -11,16 +11,15 @@ @Service @RequiredArgsConstructor -public class CustomUserDetailsService implements UserDetailsService { +public class PrincipalDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - User user = userRepository.findByUsername(username) + User principal = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); - return new org.springframework.security.core.userdetails.User(user.getUsername(), - user.getPassword(), List.of()); + return new PrincipalDetails(principal); } } \ No newline at end of file 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 8976d6a..d204dee 100644 --- a/src/main/java/org/example/siljeun/global/config/SecurityConfig.java +++ b/src/main/java/org/example/siljeun/global/config/SecurityConfig.java @@ -1,7 +1,7 @@ package org.example.siljeun.global.config; import lombok.RequiredArgsConstructor; -import org.example.siljeun.domain.user.service.CustomUserDetailsService; +import org.example.siljeun.domain.user.service.PrincipalDetailsService; import org.example.siljeun.global.security.CustomOAuth2SuccessHandler; import org.example.siljeun.global.security.JwtAuthenticationFilter; import org.example.siljeun.global.security.JwtUtil; @@ -13,39 +13,44 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.stereotype.Component; +@Component @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { private final JwtUtil jwtUtil; - private final CustomUserDetailsService userDetailsService; + private final PrincipalDetailsService userDetailsService; private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) - .formLogin(form -> form.disable()) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/auth/**", "/oauth2/**", "/login/**").permitAll() + .requestMatchers("/auth/**", "/oauth2/**", "/oauth/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .successHandler(customOAuth2SuccessHandler) - .defaultSuccessUrl("/auth/oauth2/success", true) .failureUrl("/auth/oauth2/failure") ) - .addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), - UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtUtil, userDetailsService); + } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + } \ No newline at end of file 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 f4f70c2..dff18e0 100644 --- a/src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java +++ b/src/main/java/org/example/siljeun/global/security/CustomOAuth2SuccessHandler.java @@ -1,6 +1,7 @@ package org.example.siljeun.global.security; import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @@ -20,20 +21,25 @@ public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - // 1. principal에서 사용자 정보 추출 + // principal에서 사용자 정보 추출 OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); String username = "kakao_" + oAuth2User.getAttribute("id").toString(); - // 2. JWT 생성 + // JWT 생성 String token = jwtUtil.createToken(username); - // 3. JSON 응답 세팅 - response.setStatus(HttpServletResponse.SC_OK); - response.setContentType("application/json;charset=UTF-8"); + // JWT를 HttpOnly 쿠키로 설정 + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setSecure(false); // 개발 환경에서는 false, HTTPS 환경에서는 true + cookie.setPath("/"); + cookie.setMaxAge(60 * 60); // 1시간 + + response.addCookie(cookie); - // 4. JWT를 JSON 응답 바디로 내려주기 - String json = String.format("{\"token\":\"%s\"}", token); - response.getWriter().write(json); + // 응답 상태만 OK로 설정 + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("{\"message\": \"Login successful\"}"); response.getWriter().flush(); } diff --git a/src/main/java/org/example/siljeun/global/security/JwtAuthenticationFilter.java b/src/main/java/org/example/siljeun/global/security/JwtAuthenticationFilter.java index e8334a3..a8ad690 100644 --- a/src/main/java/org/example/siljeun/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/org/example/siljeun/global/security/JwtAuthenticationFilter.java @@ -1,6 +1,5 @@ package org.example.siljeun.global.security; -import jakarta.annotation.Nonnull; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -8,7 +7,7 @@ import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.siljeun.domain.user.service.CustomUserDetailsService; +import org.example.siljeun.domain.user.service.PrincipalDetailsService; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -20,15 +19,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; - private final CustomUserDetailsService userDetailsService; + private final PrincipalDetailsService userDetailsService; @Override - protected void doFilterInternal( - @Nonnull HttpServletRequest request, - @Nonnull HttpServletResponse response, - @Nonnull FilterChain filterChain - ) throws ServletException, IOException { - String token = resolveToken(request); + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String token = resolveTokenFromHeader(request); if (token != null) { if (jwtUtil.validateToken(token)) { @@ -53,14 +49,23 @@ protected void doFilterInternal( filterChain.doFilter(request, response); } - private String resolveToken(HttpServletRequest request) { + private String resolveTokenFromHeader(HttpServletRequest request) { String bearer = request.getHeader("Authorization"); - if (bearer != null && bearer.startsWith(JwtUtil.BEARER_PREFIX)) { return bearer.substring(JwtUtil.BEARER_PREFIX.length()); } - return null; } +// private String resolveTokenFromCookie(HttpServletRequest request) { +// if (request.getCookies() != null) { +// return Arrays.stream(request.getCookies()) +// .filter(cookie -> cookie.getName().equals("token")) +// .map(Cookie::getValue) +// .findFirst() +// .orElse(null); +// } +// return null; +// } + } \ No newline at end of file diff --git a/src/main/java/org/example/siljeun/global/security/CustomUserDetails.java b/src/main/java/org/example/siljeun/global/security/PrincipalDetails.java similarity index 92% rename from src/main/java/org/example/siljeun/global/security/CustomUserDetails.java rename to src/main/java/org/example/siljeun/global/security/PrincipalDetails.java index aa82025..40c0d2a 100644 --- a/src/main/java/org/example/siljeun/global/security/CustomUserDetails.java +++ b/src/main/java/org/example/siljeun/global/security/PrincipalDetails.java @@ -9,7 +9,7 @@ import org.springframework.security.core.userdetails.UserDetails; @RequiredArgsConstructor -public class CustomUserDetails implements UserDetails { +public class PrincipalDetails implements UserDetails { private final User user; @@ -19,7 +19,7 @@ public Long getUserId() { @Override public String getUsername() { - return ""; + return user.getUsername(); } @Override