From a59200821a4064c482f6abcc87cda4990e8ed156 Mon Sep 17 00:00:00 2001 From: yeyun0423 Date: Mon, 1 Dec 2025 17:59:40 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Feat:=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85?= =?UTF-8?q?=20Session=20=EB=B0=A9=EC=8B=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++ .../user/controller/UserController.java | 21 +++++++ .../domain/user/converter/UserConverter.java | 26 +++++++++ .../domain/user/dto/req/UserReqDTO.java | 34 ++++++++++++ .../domain/user/dto/res/UserResDTO.java | 21 +++++++ .../domain/user/entity/User.java | 10 ++++ .../user/service/UserCommandService.java | 10 ++++ .../service/impl/UserCommandServiceImpl.java | 40 ++++++++++++++ .../global/auth/enums/Role.java | 5 ++ .../global/config/SecurityConfig.java | 55 +++++++++++++++++++ 10 files changed, 226 insertions(+) create mode 100644 src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java create mode 100644 src/main/java/com/example/umc_9th_springboot/domain/user/converter/UserConverter.java create mode 100644 src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java create mode 100644 src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java create mode 100644 src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java create mode 100644 src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java create mode 100644 src/main/java/com/example/umc_9th_springboot/global/auth/enums/Role.java create mode 100644 src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java diff --git a/build.gradle b/build.gradle index d36aabf..7078374 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,10 @@ dependencies { // Validation implementation 'org.springframework.boot:spring-boot-starter-validation' + + // Security + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' } tasks.named('test') { diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java new file mode 100644 index 0000000..cdad629 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java @@ -0,0 +1,21 @@ +package com.example.umc_9th_springboot.domain.user.controller; + +import com.example.umc_9th_springboot.domain.user.dto.req.UserReqDTO; +import com.example.umc_9th_springboot.domain.user.dto.res.UserResDTO; +import com.example.umc_9th_springboot.domain.user.service.UserCommandService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/users") +@RequiredArgsConstructor +public class UserController { + + private final UserCommandService userCommandService; + + //회원가입 API + @PostMapping("/sign-up") + public UserResDTO.SignUpDTO signUp(@RequestBody UserReqDTO.SignUpDTO request) { + return userCommandService.signup(request); + } +} diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/converter/UserConverter.java b/src/main/java/com/example/umc_9th_springboot/domain/user/converter/UserConverter.java new file mode 100644 index 0000000..2764cc9 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/converter/UserConverter.java @@ -0,0 +1,26 @@ +package com.example.umc_9th_springboot.domain.user.converter; + +import com.example.umc_9th_springboot.domain.user.dto.req.UserReqDTO; +import com.example.umc_9th_springboot.domain.user.entity.User; +import com.example.umc_9th_springboot.global.auth.enums.Role; + +import java.time.LocalDate; + +public class UserConverter { + public static User toUser( + UserReqDTO.SignUpDTO request, + String encodedPassword, + Role role + ) { + return User.builder() + .email(request.getEmail()) + .password(encodedPassword) + .name(request.getName()) + .gender(request.getGender()) + .birth(LocalDate.parse(request.getBirth())) + .address(request.getAddress()) + .role(role) + .point(0) + .build(); + } +} diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java new file mode 100644 index 0000000..c6778a1 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java @@ -0,0 +1,34 @@ +package com.example.umc_9th_springboot.domain.user.dto.req; + +import com.example.umc_9th_springboot.domain.user.enums.Gender; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Builder; + +public class UserReqDTO { + + //회원가입 요청 DTO + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class SignUpDTO { + private String email; + private String password; + private String name; + private Gender gender; + private String birth; // LocalDate로 변환 필요 + private String address; + } + + //로그인 요청 DTO + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class LoginDTO { + private String email; + private String password; + } +} diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java new file mode 100644 index 0000000..40f7a17 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java @@ -0,0 +1,21 @@ +package com.example.umc_9th_springboot.domain.user.dto.res; +import com.example.umc_9th_springboot.global.auth.enums.Role; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class UserResDTO { + + //회원가입 응답 DTO + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class SignUpDTO { + private Long userId; + private String email; + private String name; + private Role role; + } +} diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/entity/User.java b/src/main/java/com/example/umc_9th_springboot/domain/user/entity/User.java index 02d25fc..b113a27 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/entity/User.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/entity/User.java @@ -7,6 +7,7 @@ import com.example.umc_9th_springboot.domain.shop.entity.UserRegion; import com.example.umc_9th_springboot.domain.review.entity.Review; import com.example.umc_9th_springboot.domain.review.entity.ReviewComment; +import com.example.umc_9th_springboot.global.auth.enums.Role; import jakarta.persistence.*; import lombok.*; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -31,6 +32,15 @@ public class User extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(nullable = false, unique = true) + private String email; + + @Column(nullable = false) + private String password; + + @Enumerated(EnumType.STRING) + private Role role; + @Column(nullable = false, length = 20) private String name; diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java new file mode 100644 index 0000000..960f2db --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java @@ -0,0 +1,10 @@ +package com.example.umc_9th_springboot.domain.user.service; + +import com.example.umc_9th_springboot.domain.user.dto.req.UserReqDTO; +import com.example.umc_9th_springboot.domain.user.dto.res.UserResDTO; + +public interface UserCommandService { + + //회원가입 + UserResDTO.SignUpDTO signup(UserReqDTO.SignUpDTO dto); +} diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java new file mode 100644 index 0000000..8098f1a --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java @@ -0,0 +1,40 @@ +package com.example.umc_9th_springboot.domain.user.service.impl; + +import com.example.umc_9th_springboot.domain.user.converter.UserConverter; +import com.example.umc_9th_springboot.domain.user.dto.req.UserReqDTO; +import com.example.umc_9th_springboot.domain.user.dto.res.UserResDTO; +import com.example.umc_9th_springboot.domain.user.entity.User; +import com.example.umc_9th_springboot.domain.user.repository.UserRepository; +import com.example.umc_9th_springboot.domain.user.service.UserCommandService; +import com.example.umc_9th_springboot.global.auth.enums.Role; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +public class UserCommandServiceImpl implements UserCommandService { + + private final UserRepository userRepository; + + private final PasswordEncoder passwordEncoder; + + //회원가입 + @Override + public UserResDTO.SignUpDTO signup(UserReqDTO.SignUpDTO dto) { + + String encodedPassword = passwordEncoder.encode(dto.getPassword()); + + User user = UserConverter.toUser(dto, encodedPassword, Role.USER); + + User savedUser = userRepository.save(user); + + return UserResDTO.SignUpDTO.builder() + .userId(savedUser.getId()) + .email(savedUser.getEmail()) + .name(savedUser.getName()) + .role(savedUser.getRole()) + .build(); + } +} diff --git a/src/main/java/com/example/umc_9th_springboot/global/auth/enums/Role.java b/src/main/java/com/example/umc_9th_springboot/global/auth/enums/Role.java new file mode 100644 index 0000000..4ab46fb --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/global/auth/enums/Role.java @@ -0,0 +1,5 @@ +package com.example.umc_9th_springboot.global.auth.enums; + +public enum Role { + ROLE_ADMIN, USER, ROLE_USER +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java new file mode 100644 index 0000000..005c351 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java @@ -0,0 +1,55 @@ +package com.example.umc_9th_springboot.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@Configuration +public class SecurityConfig { + + private final String[] allowUris = { + "/swagger-ui/**", + "/swagger-resources/**", + "/v3/api-docs/**", + "/webjars/**", + "/api/users/sign-up" + }; + + // PasswordEncoder 빈 등록 + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + // Security 필터 체인 설정 + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) // CSRF 비활성화 + + .authorizeHttpRequests(requests -> requests + .requestMatchers(allowUris).permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) + + .formLogin(form -> form + .defaultSuccessUrl("/swagger-ui/index.html", true) + .permitAll() + ) + + .logout(logout -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll() + ); + + return http.build(); + } +} From bd1cf560257981b739b802c949a20e61acc8aa43 Mon Sep 17 00:00:00 2001 From: yeyun0423 Date: Mon, 1 Dec 2025 18:07:05 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Refact:=20=EC=97=94=EB=93=9C=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java index cdad629..5b51d21 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java @@ -14,7 +14,7 @@ public class UserController { private final UserCommandService userCommandService; //회원가입 API - @PostMapping("/sign-up") + @PostMapping("/sign-up/session") public UserResDTO.SignUpDTO signUp(@RequestBody UserReqDTO.SignUpDTO request) { return userCommandService.signup(request); } From 1921a8a2c870923218b3b434086f20fb20c5884a Mon Sep 17 00:00:00 2001 From: yeyun0423 Date: Mon, 1 Dec 2025 18:29:02 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Feat:=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=EB=B0=A9=EC=8B=9D=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 20 +++++++++++++++++- .../domain/user/dto/res/UserResDTO.java | 13 ++++++++++++ .../user/repository/UserRepository.java | 3 +++ .../user/service/UserCommandService.java | 2 ++ .../service/impl/UserCommandServiceImpl.java | 21 +++++++++++++++++++ .../global/config/SecurityConfig.java | 3 ++- 6 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java index 5b51d21..5dd37a9 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java @@ -3,6 +3,8 @@ import com.example.umc_9th_springboot.domain.user.dto.req.UserReqDTO; import com.example.umc_9th_springboot.domain.user.dto.res.UserResDTO; import com.example.umc_9th_springboot.domain.user.service.UserCommandService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -13,9 +15,25 @@ public class UserController { private final UserCommandService userCommandService; - //회원가입 API + //회원가입 세션 API @PostMapping("/sign-up/session") public UserResDTO.SignUpDTO signUp(@RequestBody UserReqDTO.SignUpDTO request) { return userCommandService.signup(request); } + + // 로그인 세션 API + @PostMapping("/login/session") + public UserResDTO.LoginDTO loginSession( + @RequestBody UserReqDTO.LoginDTO request, + HttpServletRequest httpRequest + ) { + UserResDTO.LoginDTO response = userCommandService.login(request); + + // 세션 생성 및 사용자 정보 저장 + HttpSession session = httpRequest.getSession(true); + session.setAttribute("loginUserId", response.getUserId()); + session.setAttribute("loginUserEmail", response.getEmail()); + + return response; + } } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java index 40f7a17..49544a9 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java @@ -18,4 +18,17 @@ public static class SignUpDTO { private String name; private Role role; } + + //로그인 응답 DTO + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class LoginDTO { + private Long userId; + private String email; + private String name; + private Role role; + } + } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/repository/UserRepository.java b/src/main/java/com/example/umc_9th_springboot/domain/user/repository/UserRepository.java index 6a26a5d..6414849 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/repository/UserRepository.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/repository/UserRepository.java @@ -9,6 +9,9 @@ public interface UserRepository extends JpaRepository { + // 이메일로 조회 + Optional findByEmail(String email); + //마이페이지 회원 정보 조회 Optional findById(Long id); } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java index 960f2db..1e82c91 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java @@ -7,4 +7,6 @@ public interface UserCommandService { //회원가입 UserResDTO.SignUpDTO signup(UserReqDTO.SignUpDTO dto); + // 로그인 + UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto); } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java index 8098f1a..063b0a5 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java @@ -11,6 +11,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.NoSuchElementException; + @Service @RequiredArgsConstructor @@ -37,4 +39,23 @@ public UserResDTO.SignUpDTO signup(UserReqDTO.SignUpDTO dto) { .role(savedUser.getRole()) .build(); } + + // 로그인 + @Override + public UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto) { + + User user = userRepository.findByEmail(dto.getEmail()) + .orElseThrow(() -> new NoSuchElementException("존재하지 않는 회원입니다.")); + + if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + } + + return UserResDTO.LoginDTO.builder() + .userId(user.getId()) + .email(user.getEmail()) + .name(user.getName()) + .role(user.getRole()) + .build(); + } } diff --git a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java index 005c351..087e538 100644 --- a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java +++ b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java @@ -18,7 +18,8 @@ public class SecurityConfig { "/swagger-resources/**", "/v3/api-docs/**", "/webjars/**", - "/api/users/sign-up" + "/api/users/sign-up/session", + "/api/users/login/session" }; // PasswordEncoder 빈 등록 From 7c1aab1219f3f9b66c0ce57ffccde8ba31b1953a Mon Sep 17 00:00:00 2001 From: yeyun0423 Date: Mon, 1 Dec 2025 18:37:53 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Feat:=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83?= =?UTF-8?q?=20=EC=84=B8=EC=85=98=EB=B0=A9=EC=8B=9D=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/controller/UserController.java | 8 ++++++++ .../umc_9th_springboot/global/config/SecurityConfig.java | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java index 5dd37a9..0542e76 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java @@ -36,4 +36,12 @@ public UserResDTO.LoginDTO loginSession( return response; } + // 로그아웃 세션 방식 + @PostMapping("/logout/session") + public void logoutSession(HttpServletRequest httpRequest) { + HttpSession session = httpRequest.getSession(false); + if (session != null) { + session.invalidate(); + } + } } diff --git a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java index 087e538..3590f14 100644 --- a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java +++ b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java @@ -19,7 +19,8 @@ public class SecurityConfig { "/v3/api-docs/**", "/webjars/**", "/api/users/sign-up/session", - "/api/users/login/session" + "/api/users/login/session", + "/api/users/logout/session" }; // PasswordEncoder 빈 등록 From 8b18b3fd0586fbfc33dba438b898d19ab2728406 Mon Sep 17 00:00:00 2001 From: yeyun0423 Date: Mon, 1 Dec 2025 19:49:12 +0900 Subject: [PATCH 5/6] =?UTF-8?q?Feat:=EB=A1=9C=EA=B7=B8=EC=9D=B8=20jwt?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++ .../user/controller/UserController.java | 14 +++- .../domain/user/dto/req/UserReqDTO.java | 17 +++- .../domain/user/dto/res/UserResDTO.java | 13 ++- .../user/service/UserCommandService.java | 9 +- .../service/impl/UserCommandServiceImpl.java | 33 +++++++- .../service/CustomUserDetailsService.java | 29 +++++++ .../global/auth/util/CustomUserDetails.java | 43 ++++++++++ .../global/auth/util/JwtAuthFilter.java | 80 ++++++++++++++++++ .../global/auth/util/JwtUtil.java | 82 +++++++++++++++++++ .../global/config/SecurityConfig.java | 18 +++- src/main/resources/application.yml | 6 ++ 12 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/example/umc_9th_springboot/global/auth/service/CustomUserDetailsService.java create mode 100644 src/main/java/com/example/umc_9th_springboot/global/auth/util/CustomUserDetails.java create mode 100644 src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtAuthFilter.java create mode 100644 src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtUtil.java diff --git a/build.gradle b/build.gradle index 7078374..8ac929f 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,12 @@ dependencies { // Security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + + // Jwt + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + implementation 'org.springframework.boot:spring-boot-configuration-processor' } tasks.named('test') { diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java index 0542e76..c4a96df 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java @@ -23,11 +23,11 @@ public UserResDTO.SignUpDTO signUp(@RequestBody UserReqDTO.SignUpDTO request) { // 로그인 세션 API @PostMapping("/login/session") - public UserResDTO.LoginDTO loginSession( - @RequestBody UserReqDTO.LoginDTO request, + public UserResDTO.LoginSessionDTO loginSession( + @RequestBody UserReqDTO.LoginSessionDTO request, HttpServletRequest httpRequest ) { - UserResDTO.LoginDTO response = userCommandService.login(request); + UserResDTO.LoginSessionDTO response = userCommandService.login(request); // 세션 생성 및 사용자 정보 저장 HttpSession session = httpRequest.getSession(true); @@ -44,4 +44,12 @@ public void logoutSession(HttpServletRequest httpRequest) { session.invalidate(); } } + + // 로그인 JWT 방식 API + @PostMapping("/login/jwt") + public UserResDTO.LoginJwtDTO loginJwt( + @RequestBody UserReqDTO.LoginJwtDTO request + ) { + return userCommandService.loginJwt(request); + } } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java index c6778a1..05af230 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/req/UserReqDTO.java @@ -1,6 +1,7 @@ package com.example.umc_9th_springboot.domain.user.dto.req; import com.example.umc_9th_springboot.domain.user.enums.Gender; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; @@ -8,7 +9,7 @@ public class UserReqDTO { - //회원가입 요청 DTO + //회원가입 세션 방식 요청 DTO @Getter @NoArgsConstructor @AllArgsConstructor @@ -18,17 +19,25 @@ public static class SignUpDTO { private String password; private String name; private Gender gender; - private String birth; // LocalDate로 변환 필요 + private String birth; private String address; } - //로그인 요청 DTO + //로그인 세션 방식 요청 DTO @Getter @NoArgsConstructor @AllArgsConstructor @Builder - public static class LoginDTO { + public static class LoginSessionDTO { private String email; private String password; } + + // 로그인 jwt 방식 요청 DTO + public record LoginJwtDTO( + @NotBlank + String email, + @NotBlank + String password + ) {} } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java index 49544a9..c39d901 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java @@ -7,7 +7,7 @@ public class UserResDTO { - //회원가입 응답 DTO + //회원가입 세션 방식 응답 DTO @Getter @NoArgsConstructor @AllArgsConstructor @@ -19,16 +19,23 @@ public static class SignUpDTO { private Role role; } - //로그인 응답 DTO + //로그인 세션 방식 응답 DTO @Getter @NoArgsConstructor @AllArgsConstructor @Builder - public static class LoginDTO { + public static class LoginSessionDTO { private Long userId; private String email; private String name; private Role role; } + // 로그인 jwt 방식 응답 DTO + @Builder + public record LoginJwtDTO( + Long userId, + String accessToken + ) {} + } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java index 1e82c91..d232e20 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java @@ -5,8 +5,11 @@ public interface UserCommandService { - //회원가입 + // Session 회원가입 UserResDTO.SignUpDTO signup(UserReqDTO.SignUpDTO dto); - // 로그인 - UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto); + // Session 로그인 + UserResDTO.LoginSessionDTO login(UserReqDTO.LoginSessionDTO dto); + + // jwt 로그인 + UserResDTO.LoginJwtDTO loginJwt(UserReqDTO.LoginJwtDTO dto); } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java index 063b0a5..bbb767e 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java @@ -7,6 +7,7 @@ import com.example.umc_9th_springboot.domain.user.repository.UserRepository; import com.example.umc_9th_springboot.domain.user.service.UserCommandService; import com.example.umc_9th_springboot.global.auth.enums.Role; +import com.example.umc_9th_springboot.global.auth.util.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -22,6 +23,8 @@ public class UserCommandServiceImpl implements UserCommandService { private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; + //회원가입 @Override public UserResDTO.SignUpDTO signup(UserReqDTO.SignUpDTO dto) { @@ -40,9 +43,9 @@ public UserResDTO.SignUpDTO signup(UserReqDTO.SignUpDTO dto) { .build(); } - // 로그인 + // Session 로그인 @Override - public UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto) { + public UserResDTO.LoginSessionDTO login(UserReqDTO.LoginSessionDTO dto) { User user = userRepository.findByEmail(dto.getEmail()) .orElseThrow(() -> new NoSuchElementException("존재하지 않는 회원입니다.")); @@ -51,11 +54,35 @@ public UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto) { throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); } - return UserResDTO.LoginDTO.builder() + return UserResDTO.LoginSessionDTO.builder() .userId(user.getId()) .email(user.getEmail()) .name(user.getName()) .role(user.getRole()) .build(); } + + // jwt 로그인 + @Override + public UserResDTO.LoginJwtDTO loginJwt(UserReqDTO.LoginJwtDTO dto) { + + UserResDTO.LoginSessionDTO loginResult = login( + new UserReqDTO.LoginSessionDTO( + dto.email(), + dto.password() + ) + ); + + String accessToken = jwtUtil.createAccessToken( + loginResult.getUserId(), + loginResult.getEmail(), + loginResult.getRole().name() + ); + + return UserResDTO.LoginJwtDTO.builder() + .userId(loginResult.getUserId()) + .accessToken(accessToken) + .build(); + } + } diff --git a/src/main/java/com/example/umc_9th_springboot/global/auth/service/CustomUserDetailsService.java b/src/main/java/com/example/umc_9th_springboot/global/auth/service/CustomUserDetailsService.java new file mode 100644 index 0000000..cef0057 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/global/auth/service/CustomUserDetailsService.java @@ -0,0 +1,29 @@ +package com.example.umc_9th_springboot.global.auth.service; + +import com.example.umc_9th_springboot.domain.user.entity.User; +import com.example.umc_9th_springboot.domain.user.repository.UserRepository; +import com.example.umc_9th_springboot.global.auth.util.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) + throws UsernameNotFoundException { + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> + new UsernameNotFoundException("해당 이메일을 가진 유저가 없습니다: " + email) + ); + + return new CustomUserDetails(user); + } +} diff --git a/src/main/java/com/example/umc_9th_springboot/global/auth/util/CustomUserDetails.java b/src/main/java/com/example/umc_9th_springboot/global/auth/util/CustomUserDetails.java new file mode 100644 index 0000000..ea60e40 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/global/auth/util/CustomUserDetails.java @@ -0,0 +1,43 @@ +package com.example.umc_9th_springboot.global.auth.util; + +import com.example.umc_9th_springboot.domain.user.entity.User; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +@Getter +public class CustomUserDetails implements UserDetails { + + private final User user; + + public CustomUserDetails(User user){ + this.user = user; + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())); + } + + @Override + public String getPassword() { return user.getPassword(); } + + @Override + public String getUsername() { return user.getEmail(); } + + @Override + public boolean isAccountNonExpired() { return true; } + + @Override + public boolean isAccountNonLocked() { return true; } + + @Override + public boolean isCredentialsNonExpired() { return true; } + + @Override + public boolean isEnabled() { return true; } +} diff --git a/src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtAuthFilter.java b/src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtAuthFilter.java new file mode 100644 index 0000000..e769e6d --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtAuthFilter.java @@ -0,0 +1,80 @@ +package com.example.umc_9th_springboot.global.auth.util; + +import com.example.umc_9th_springboot.global.apiPayload.ApiResponse; +import com.example.umc_9th_springboot.global.apiPayload.code.GeneralErrorCode; +import com.example.umc_9th_springboot.global.auth.service.CustomUserDetailsService; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final CustomUserDetailsService customUserDetailsService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + + try { + // 토큰 가져오기 + String token = request.getHeader("Authorization"); + + // token이 없거나 Bearer가 아니면 넘기기 + if (token == null || !token.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + // Bearer이면 추출 + token = token.replace("Bearer ", ""); + + // AccessToken 검증하기 + if (jwtUtil.isValid(token)) { + // 토큰에서 이메일 추출 + String email = jwtUtil.getEmail(token); + + // 인증 객체 생성 + UserDetails user = customUserDetailsService.loadUserByUsername(email); + Authentication auth = new UsernamePasswordAuthenticationToken( + user, + null, + user.getAuthorities() + ); + + // SecurityContext에 인증 저장 + SecurityContextHolder.getContext().setAuthentication(auth); + } + + filterChain.doFilter(request, response); + } catch (Exception e) { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ApiResponse errorResponse = ApiResponse.onFailure( + GeneralErrorCode.UNAUTHORIZED, + null + ); + + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(response.getOutputStream(), errorResponse); + } + } +} diff --git a/src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtUtil.java b/src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtUtil.java new file mode 100644 index 0000000..82c46a4 --- /dev/null +++ b/src/main/java/com/example/umc_9th_springboot/global/auth/util/JwtUtil.java @@ -0,0 +1,82 @@ +package com.example.umc_9th_springboot.global.auth.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; + +@Component +public class JwtUtil { + + private SecretKey secretKey; + private final String secret; + private final Duration accessExpiration; + + // application.yml 의 jwt.token.* 값 주입 + public JwtUtil( + @Value("${jwt.token.secretKey}") String secret, + @Value("${jwt.token.expiration.access}") Long accessExpiration + ) { + this.secret = secret; + this.accessExpiration = Duration.ofMillis(accessExpiration); + } + + @PostConstruct + public void init() { + this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + // AccessToken 생성 + public String createAccessToken(Long userId, String email, String role) { + return createToken(userId, email, role, accessExpiration); + } + + public String getEmail(String token) { + try { + return getClaims(token).getPayload().get("email", String.class); + } catch (JwtException e) { + return null; + } + } + + public boolean isValid(String token) { + try { + getClaims(token); + return true; + } catch (JwtException e) { + return false; + } + } + + + private String createToken(Long userId, String email, String role, Duration expiration) { + Instant now = Instant.now(); + + return Jwts.builder() + .subject(String.valueOf(userId)) + .claim("email", email) + .claim("role", role) + .issuedAt(Date.from(now)) + .expiration(Date.from(now.plus(expiration))) + .signWith(secretKey) + .compact(); + } + + private Jws getClaims(String token) throws JwtException { + return Jwts.parser() + .verifyWith(secretKey) + .clockSkewSeconds(60) + .build() + .parseSignedClaims(token); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java index 3590f14..bd06242 100644 --- a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java +++ b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java @@ -1,5 +1,7 @@ package com.example.umc_9th_springboot.global.config; +import com.example.umc_9th_springboot.global.auth.util.JwtAuthFilter; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -8,11 +10,16 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + @EnableWebSecurity @Configuration +@RequiredArgsConstructor public class SecurityConfig { + private final JwtAuthFilter jwtAuthFilter; + private final String[] allowUris = { "/swagger-ui/**", "/swagger-resources/**", @@ -20,7 +27,8 @@ public class SecurityConfig { "/webjars/**", "/api/users/sign-up/session", "/api/users/login/session", - "/api/users/logout/session" + "/api/users/logout/session", + "/api/users/login/jwt" }; // PasswordEncoder 빈 등록 @@ -33,7 +41,7 @@ public PasswordEncoder passwordEncoder() { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(AbstractHttpConfigurer::disable) // CSRF 비활성화 + .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(requests -> requests .requestMatchers(allowUris).permitAll() @@ -50,8 +58,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") .permitAll() - ); + ) + + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } -} +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4df69a7..25a5c57 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -23,3 +23,9 @@ spring: properties: hibernate: format_sql: true + +jwt: + token: + secretKey: ${JWT_SECRET_KEY} + expiration: + access: ${JWT_ACCESS_EXPIRE} From 89943ce590872693b225406bbaa2510c06b855e8 Mon Sep 17 00:00:00 2001 From: yeyun0423 Date: Mon, 1 Dec 2025 20:00:38 +0900 Subject: [PATCH 6/6] =?UTF-8?q?Feat:=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85?= =?UTF-8?q?=20jwt=20=EB=B0=A9=EC=8B=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 6 +++++ .../domain/user/dto/res/UserResDTO.java | 12 +++++++++ .../user/service/UserCommandService.java | 3 +++ .../service/impl/UserCommandServiceImpl.java | 27 +++++++++++++++++++ .../global/config/SecurityConfig.java | 3 ++- 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java index c4a96df..5294b58 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/controller/UserController.java @@ -52,4 +52,10 @@ public UserResDTO.LoginJwtDTO loginJwt( ) { return userCommandService.loginJwt(request); } + + // jwt 회원가입 API + @PostMapping("/sign-up/jwt") + public UserResDTO.SignUpJwtDTO signUpJwt(@RequestBody UserReqDTO.SignUpDTO request) { + return userCommandService.signupJwt(request); + } } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java index c39d901..6a9fe23 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/dto/res/UserResDTO.java @@ -38,4 +38,16 @@ public record LoginJwtDTO( String accessToken ) {} + // jwt 회원가입 응답 DTO + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class SignUpJwtDTO { + private Long userId; + private String email; + private String name; + private Role role; + private String accessToken; + } } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java index d232e20..296bce9 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/UserCommandService.java @@ -12,4 +12,7 @@ public interface UserCommandService { // jwt 로그인 UserResDTO.LoginJwtDTO loginJwt(UserReqDTO.LoginJwtDTO dto); + + // jwt 회원가입 + UserResDTO.SignUpJwtDTO signupJwt(UserReqDTO.SignUpDTO dto); } diff --git a/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java index bbb767e..11cda96 100644 --- a/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java +++ b/src/main/java/com/example/umc_9th_springboot/domain/user/service/impl/UserCommandServiceImpl.java @@ -85,4 +85,31 @@ public UserResDTO.LoginJwtDTO loginJwt(UserReqDTO.LoginJwtDTO dto) { .build(); } + // jwt 회원가입 + @Override + public UserResDTO.SignUpJwtDTO signupJwt(UserReqDTO.SignUpDTO dto) { + + String encodedPassword = passwordEncoder.encode(dto.getPassword()); + + User user = UserConverter.toUser(dto, encodedPassword, Role.USER); + + User savedUser = userRepository.save(user); + + + String accessToken = jwtUtil.createAccessToken( + savedUser.getId(), + savedUser.getEmail(), + savedUser.getRole().name() + ); + + + return UserResDTO.SignUpJwtDTO.builder() + .userId(savedUser.getId()) + .email(savedUser.getEmail()) + .name(savedUser.getName()) + .role(savedUser.getRole()) + .accessToken(accessToken) + .build(); + } + } diff --git a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java index bd06242..b01194c 100644 --- a/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java +++ b/src/main/java/com/example/umc_9th_springboot/global/config/SecurityConfig.java @@ -28,7 +28,8 @@ public class SecurityConfig { "/api/users/sign-up/session", "/api/users/login/session", "/api/users/logout/session", - "/api/users/login/jwt" + "/api/users/login/jwt", + "/api/users/sign-up/jwt" }; // PasswordEncoder 빈 등록