From af5813fa9a12fe6399ed8d831df19527a7b7fce0 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:28:26 +0900 Subject: [PATCH 01/20] =?UTF-8?q?docs:=20Spring=20Security=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=EC=9D=84=20build.gradle=EC=97=90=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Joonseok/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Joonseok/build.gradle b/Joonseok/build.gradle index 55506486..ce009fae 100644 --- a/Joonseok/build.gradle +++ b/Joonseok/build.gradle @@ -20,6 +20,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-webmvc' + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' From 73f1ff8e6be2f4094ccfdfe46447dc1213c23231 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:34:09 +0900 Subject: [PATCH 02/20] =?UTF-8?q?Feat:=20PW=20=EC=95=94=ED=98=B8=ED=99=94?= =?UTF-8?q?=20=EB=B9=88,=20CSRF=20=EC=84=A4=EC=A0=95=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94,=20Swagger=20=EA=B4=80=EB=A0=A8=20URL?= =?UTF-8?q?=EA=B3=BC=20=EC=9D=B8=EC=A6=9D-=EC=9D=B8=EA=B0=80=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20API=EC=97=90=20=EB=8C=80=ED=95=B4=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EB=B6=88=ED=95=84=EC=9A=94=20=EC=84=A0=EC=96=B8,?= =?UTF-8?q?=20form=20=EA=B8=B0=EB=B0=98=20login-logout=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/global/config/SecurityConfig.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java diff --git a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java new file mode 100644 index 00000000..6d309383 --- /dev/null +++ b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java @@ -0,0 +1,47 @@ +package com.umc.study.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/**", + "/auth/**" // sign-up, login request allow + }; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(request -> request + .requestMatchers(allowUris).permitAll() + .anyRequest().authenticated() + ) + .formLogin(form -> form + .defaultSuccessUrl("/swagger-ui/index-html", true) + .permitAll()) + .logout(logout -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll()); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} From 3a7cb8563dc5a1ad1d2daf89a11031dd395c70ac Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:38:25 +0900 Subject: [PATCH 03/20] =?UTF-8?q?Feat:=20User=20=EC=97=94=ED=84=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20=EB=88=84=EB=9D=BD=EB=90=98=EC=96=B4=20?= =?UTF-8?q?=EC=9E=88=EC=97=88=EB=8D=98=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/umc/study/domain/user/entity/User.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Joonseok/src/main/java/com/umc/study/domain/user/entity/User.java b/Joonseok/src/main/java/com/umc/study/domain/user/entity/User.java index 16add48e..c0caf445 100644 --- a/Joonseok/src/main/java/com/umc/study/domain/user/entity/User.java +++ b/Joonseok/src/main/java/com/umc/study/domain/user/entity/User.java @@ -36,6 +36,9 @@ public class User extends BaseEntity { @Column(nullable = false) private String email; + @Column(nullable = false) + private String password; + @Column(nullable = false) private String phoneNum; From 35efc5bfd61b4fdfef05726e5c2fc1d1696a486d Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:41:48 +0900 Subject: [PATCH 04/20] =?UTF-8?q?Feat:=20UserRepository=EC=97=90=20unique?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=EC=9D=B8=20Email=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EA=B0=92=EC=9C=BC=EB=A1=9C=20findBy=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C,=20=EB=B0=98=ED=99=98=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9D=84=20Optional=EB=A1=9C=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 --- .../com/umc/study/domain/user/repository/UserRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Joonseok/src/main/java/com/umc/study/domain/user/repository/UserRepository.java b/Joonseok/src/main/java/com/umc/study/domain/user/repository/UserRepository.java index 530877b7..d4248475 100644 --- a/Joonseok/src/main/java/com/umc/study/domain/user/repository/UserRepository.java +++ b/Joonseok/src/main/java/com/umc/study/domain/user/repository/UserRepository.java @@ -20,4 +20,5 @@ select count(mh) """) long countCompletedMissions(@Param("userId") Long userId); + Optional findByEmail(String username); } From b9488a14c71dd140ef6888e8984d9340215255ac Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:42:42 +0900 Subject: [PATCH 05/20] =?UTF-8?q?Feat:=20Security=20Context=20Holder?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20AuthUser=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20UserDetail?= =?UTF-8?q?s=EC=9D=98=20=EC=9E=90=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/entity/AuthUser.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Joonseok/src/main/java/com/umc/study/global/security/entity/AuthUser.java diff --git a/Joonseok/src/main/java/com/umc/study/global/security/entity/AuthUser.java b/Joonseok/src/main/java/com/umc/study/global/security/entity/AuthUser.java new file mode 100644 index 00000000..b1afe0ca --- /dev/null +++ b/Joonseok/src/main/java/com/umc/study/global/security/entity/AuthUser.java @@ -0,0 +1,33 @@ +package com.umc.study.global.security.entity; + +import com.umc.study.domain.user.entity.User; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +@Getter +@RequiredArgsConstructor +public class AuthUser implements UserDetails { + + private final User user; + + @Override + public Collection getAuthorities() { + return List.of(); + } + + @Override + public @Nullable String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getEmail(); + } +} From 6ff9a1a8d6c73c2634c288a8812148fc7f7adf83 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:43:16 +0900 Subject: [PATCH 06/20] =?UTF-8?q?Feat:=20Security=20Context=20Holder?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20CustomUserDetailsService=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EB=A5=BC=20UserDetailsService=EC=9D=98=20=EC=9E=90=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CustomUserDetailsService.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java diff --git a/Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java b/Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java new file mode 100644 index 00000000..f3399bc1 --- /dev/null +++ b/Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java @@ -0,0 +1,27 @@ +package com.umc.study.global.security.service; + +import com.umc.study.domain.user.entity.User; +import com.umc.study.domain.user.exception.UserNotFoundException; +import com.umc.study.domain.user.repository.UserRepository; +import com.umc.study.global.security.entity.AuthUser; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +@Getter +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + User user = userRepository.findByEmail(username) + .orElseThrow(UserNotFoundException::new); + + return new AuthUser(user); + } +} From 3ff9b526474f72e1e94407bf59942d004c89f7ed Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:49:25 +0900 Subject: [PATCH 07/20] =?UTF-8?q?Feat:=20=EA=B6=8C=ED=95=9C=EC=9D=B4=20?= =?UTF-8?q?=EB=B6=80=EC=97=AC=EB=90=98=EC=A7=80=20=EC=95=8A=EC=95=84=20?= =?UTF-8?q?=EC=9D=B8=EA=B0=80=20=EB=8B=A8=EA=B3=84=EC=97=90=EC=84=9C=20For?= =?UTF-8?q?bidden=20=EC=9D=91=EB=8B=B5=EC=9D=84=20JSON=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=EC=9C=BC=EB=A1=9C=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20AccessDeniedHandler?= =?UTF-8?q?=EB=A5=BC=20=EA=B5=AC=ED=98=84=ED=95=9C=20Custom=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/CustomAccessDenied.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java diff --git a/Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java b/Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java new file mode 100644 index 00000000..58be6f1e --- /dev/null +++ b/Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java @@ -0,0 +1,37 @@ +package com.umc.study.global.security.exception; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.umc.study.global.apiPayload.ApiResponse; +import com.umc.study.global.apiPayload.code.BaseResponseCode; +import com.umc.study.global.apiPayload.code.GeneralErrorCode; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +import java.io.IOException; + +@RequiredArgsConstructor +public class CustomAccessDenied implements AccessDeniedHandler { + + private final ObjectMapper objectMapper; + + @Override + public void handle( + HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException + ) throws IOException, ServletException { + + BaseResponseCode errorCode = GeneralErrorCode.FORBIDDEN; + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(errorCode.getStatus().value()); + + ApiResponse errorResponse = ApiResponse.onFailure(errorCode, null); + + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + } +} From 0b5c5a81a7c41c4bca1e8c20ed5c3c13dee5ac42 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:54:48 +0900 Subject: [PATCH 08/20] =?UTF-8?q?Feat:=20permitAll=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20API=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=EC=97=90=20=EB=8C=80=ED=95=B4=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=ED=86=A0=ED=81=B0=20=EC=97=86=EC=9D=B4=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=ED=96=88=EC=9D=84=20=EC=8B=9C,=20=EB=8D=98=EC=A7=88?= =?UTF-8?q?=20Forbidden=20=EC=98=88=EC=99=B8=20=EC=9D=91=EB=8B=B5=EC=9D=84?= =?UTF-8?q?=20JSON=20=ED=85=9C=ED=94=8C=EB=A6=BF=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20Aut?= =?UTF-8?q?henticationEntryPoint=EC=9D=98=20=EA=B5=AC=ED=98=84=EC=B2=B4=20?= =?UTF-8?q?CustomEntryPoint=20=EA=B0=9D=EC=B2=B4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/CustomEntryPoint.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java diff --git a/Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java b/Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java new file mode 100644 index 00000000..560e9489 --- /dev/null +++ b/Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java @@ -0,0 +1,37 @@ +package com.umc.study.global.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.umc.study.global.apiPayload.ApiResponse; +import com.umc.study.global.apiPayload.code.BaseResponseCode; +import com.umc.study.global.apiPayload.code.GeneralErrorCode; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import java.io.IOException; + +@RequiredArgsConstructor +public class CustomEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException + ) throws IOException, ServletException { + + BaseResponseCode errorCode = GeneralErrorCode.UNAUTHORIZED; + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(errorCode.getStatus().value()); + + ApiResponse errorResponse = ApiResponse.onFailure(errorCode, null); + + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + } +} From d75b57b9c8b706991c0c390893982a293fb1716e Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 17:58:03 +0900 Subject: [PATCH 09/20] =?UTF-8?q?Feat:=20Forbidden,=20Unauthorized=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=9E=98=ED=8D=BC=EC=97=90=20@Component?= =?UTF-8?q?=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=EC=9D=84=20?= =?UTF-8?q?=EB=B6=80=EC=B0=A9=ED=95=98=EC=97=AC=20Bean=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=ED=95=98=EA=B2=8C=EB=81=94=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/umc/study/global/security/CustomEntryPoint.java | 2 ++ .../umc/study/global/security/exception/CustomAccessDenied.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java b/Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java index 560e9489..8922a968 100644 --- a/Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java +++ b/Joonseok/src/main/java/com/umc/study/global/security/CustomEntryPoint.java @@ -10,9 +10,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; import java.io.IOException; +@Component @RequiredArgsConstructor public class CustomEntryPoint implements AuthenticationEntryPoint { diff --git a/Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java b/Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java index 58be6f1e..e8fb2807 100644 --- a/Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java +++ b/Joonseok/src/main/java/com/umc/study/global/security/exception/CustomAccessDenied.java @@ -10,9 +10,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; import java.io.IOException; +@Component @RequiredArgsConstructor public class CustomAccessDenied implements AccessDeniedHandler { From bdb023fa9aa4760fec17a5eaa8e2fa4af0182458 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 18:08:06 +0900 Subject: [PATCH 10/20] =?UTF-8?q?Feat:=20Spring=20boot=204.x.x=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A0=9C=EA=B1=B0=EB=90=9C=20ObjectMapper=EB=A5=BC?= =?UTF-8?q?=20Bean=EC=9C=BC=EB=A1=9C=20=EB=93=B1=EB=A1=9D,=20Forbidden,=20?= =?UTF-8?q?Unauthorized=20=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC?= =?UTF-8?q?=EB=A5=BC=20SecurityConfig=EC=97=90=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/study/global/config/SecurityConfig.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java index 6d309383..1f326b16 100644 --- a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java +++ b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java @@ -1,5 +1,8 @@ package com.umc.study.global.config; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.umc.study.global.security.CustomEntryPoint; +import com.umc.study.global.security.exception.CustomAccessDenied; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -13,6 +16,11 @@ @Configuration public class SecurityConfig { + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + private final String[] allowUris = { "/swagger-ui/**", "/swagger-resources/**", @@ -21,7 +29,7 @@ public class SecurityConfig { }; @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAccessDenied customAccessDenied, CustomEntryPoint customEntryPoint) throws Exception { http .csrf(AbstractHttpConfigurer::disable) @@ -35,7 +43,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") - .permitAll()); + .permitAll()) + .exceptionHandling(e -> e + .accessDeniedHandler(customAccessDenied) + .authenticationEntryPoint(customEntryPoint)); return http.build(); } From 0a953161fcf46859e2c6c4df82f5327ddacd1591 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Sun, 17 May 2026 18:11:22 +0900 Subject: [PATCH 11/20] =?UTF-8?q?docs:=20=EC=B1=95=ED=84=B0=208=20Spring?= =?UTF-8?q?=20Security=20=ED=95=B5=EC=8B=AC=20=ED=82=A4=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Joonseok/keyword_summary/ch07.md | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Joonseok/keyword_summary/ch07.md diff --git a/Joonseok/keyword_summary/ch07.md b/Joonseok/keyword_summary/ch07.md new file mode 100644 index 00000000..86b0ac0b --- /dev/null +++ b/Joonseok/keyword_summary/ch07.md @@ -0,0 +1,58 @@ +- Spring Security가 무엇인가? + + + + MVC 구조를 사용하여 동기적으로 통신하는 서버와 Webflux를 사용하여 비동기적 반응형 서버 모두 적용 가능하고, 서버를 안전하게 보호하는데에 편리한 기능을 제공합니다. + + Java 17 이상의 JRE에서만 사용 가능하며, 별도의 Spring Security 관련 설정 파일을 두거나, 서버 클래스 로더에 Spring Security를 포함할 필요가 없습니다. + +- 인증(Authentication)vs 인가(Authorization) + + + + ### Authentication + + 인증이란 의미로, 해당 사용자가 서버에 등록되어 서버의 리소스에 접근할 수 있게 기록해두는 행위입니다. + + ### Authorization + + 인가라는 의미로, 해당 사용자가 서버에서 발급한 리소스 접근 권한이 유효한지 검증하는 행위입니다. + + ### 가장 일반적인 경우… + + 현대 서비스의 경우, 철저히 관리되는 대규모 SNS 서비스의 보안을 신뢰하며, 소셜 로그인 기능을 주력으로 밀고 나가는 경우도 많지만, 보편적이며 레거시에서는 ID와 Password를 기반으로 인증을 구현합니다. + + 이 경우에는, UsernamePasswordAuthenticationFilter라는 필터 체인 단계에서 체크하게 됩니다. + +- Stateful vs Stateless + + + + ### stateful + + 서버가 `stateful` 하다는 의미는, 서버에서 사용자를 기억한다는 의미입니다. Session 기반으로 재인증하는 서버가 이와 같이 작동하며, 온라인 뱅킹과 같이 이전 트랜잭션에 영향을 받을 수 있는 서비스의 경우 사용합니다. + + 상태 저장 트랜잭션이 중단되더라도 컨텍스트와 기록이 중단된 부분부터 복구할 수 있습니다. + + ### stateless + + `stateless`는 서버가 사용자를 기억하지 못한다는 것입니다. stateful은 전체 서비스 흐름에서 매 API 요청을 일종의 트랜잭션처럼 사용하여 사용자가 현재 어디까지 요청하였는지 확인할 수 있지만, stateless는 모든 API 요청에 대해 이전에 진행된 요청을 저장하지 않습니다. + + - RESTful + - Micro Service Archtecture \ No newline at end of file From 88e78d73f3ba4b9348fc5e96adb8c173a0d1b1b0 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:16:11 +0900 Subject: [PATCH 12/20] =?UTF-8?q?fix:=20/api=20=ED=8C=A8=EC=8A=A4=EB=A5=BC?= =?UTF-8?q?=20permitAll()=ED=95=98=EC=A7=80=20=EC=95=8A=EA=B3=A0=20?= =?UTF-8?q?=EC=9E=88=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/umc/study/global/config/SecurityConfig.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java index 1f326b16..d369841c 100644 --- a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java +++ b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java @@ -22,10 +22,10 @@ public ObjectMapper objectMapper() { } private final String[] allowUris = { - "/swagger-ui/**", - "/swagger-resources/**", - "/v3/api-docs/**", - "/auth/**" // sign-up, login request allow + "/api/swagger-ui/**", + "/api/swagger-resources/**", + "/api/v3/api-docs/**", + "/api/auth/**" // sign-up, login request allow }; @Bean From 114756971d1599f515244ce7e680fb72d576b549 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:16:37 +0900 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20sign-in=20api=EC=97=90=20auth=20pr?= =?UTF-8?q?efix=EB=A5=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/study/domain/user/web/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java b/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java index 93a2991e..b3885653 100644 --- a/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java +++ b/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java @@ -21,7 +21,7 @@ public class UserController { private final UserService userService; - @PostMapping("/sign-in") + @PostMapping("/auth/sign-in") public ResponseEntity> signIn( @Valid @RequestBody Object request ) { From 4421d200c218347154488678a124d2da4a8f1b45 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:17:25 +0900 Subject: [PATCH 14/20] =?UTF-8?q?fix:=20CustomUserDetailsService=EB=A5=BC?= =?UTF-8?q?=20=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=93=B1=EB=A1=9D=ED=95=98?= =?UTF-8?q?=EA=B2=8C=EB=81=94=20Component=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/global/security/service/CustomUserDetailsService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java b/Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java index f3399bc1..57c47cff 100644 --- a/Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java +++ b/Joonseok/src/main/java/com/umc/study/global/security/service/CustomUserDetailsService.java @@ -9,8 +9,10 @@ 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.Component; @Getter +@Component @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { From d51b5d27ac137799f24eade3c6a61fa33ec54c42 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:18:54 +0900 Subject: [PATCH 15/20] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20swagger=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8=ED=95=98=EA=B2=8C=EB=81=94=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95,=20/api=EB=A5=BC=20prefix=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/umc/study/global/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java index d369841c..83dfa31d 100644 --- a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java +++ b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java @@ -38,7 +38,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAccessDe .anyRequest().authenticated() ) .formLogin(form -> form - .defaultSuccessUrl("/swagger-ui/index-html", true) + .defaultSuccessUrl("/api/swagger-ui/index.html", true) .permitAll()) .logout(logout -> logout .logoutUrl("/logout") From e464abe91ebc019662b36405bb309b8c065b3ea0 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:22:33 +0900 Subject: [PATCH 16/20] =?UTF-8?q?docs:=208=EC=A3=BC=EC=B0=A8=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5=20=EC=A0=95=EB=A6=AC=EB=A5=BC=207=EC=A3=BC=EC=B0=A8?= =?UTF-8?q?=EB=9D=BC=EA=B3=A0=20=EB=AA=85=EB=AA=85=ED=95=9C=20=EA=B2=83=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Joonseok/keyword_summary/{ch07.md => ch08.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Joonseok/keyword_summary/{ch07.md => ch08.md} (100%) diff --git a/Joonseok/keyword_summary/ch07.md b/Joonseok/keyword_summary/ch08.md similarity index 100% rename from Joonseok/keyword_summary/ch07.md rename to Joonseok/keyword_summary/ch08.md From c889c527efbaeaf7f846a3f3763c891d3f515cb2 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:37:31 +0900 Subject: [PATCH 17/20] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9A=94=EC=B2=AD=20DTO=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/domain/user/web/dto/SignUpReq.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Joonseok/src/main/java/com/umc/study/domain/user/web/dto/SignUpReq.java diff --git a/Joonseok/src/main/java/com/umc/study/domain/user/web/dto/SignUpReq.java b/Joonseok/src/main/java/com/umc/study/domain/user/web/dto/SignUpReq.java new file mode 100644 index 00000000..77594e49 --- /dev/null +++ b/Joonseok/src/main/java/com/umc/study/domain/user/web/dto/SignUpReq.java @@ -0,0 +1,51 @@ +package com.umc.study.domain.user.web.dto; + +import com.umc.study.domain.user.entity.Food; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDate; +import java.util.List; + +public record SignUpReq( + @NotNull(message = "서비스 이용 동의 필드는 비어있을 수 없습니다.") + Agree agree, + + @NotBlank(message = "이름 필드는 비어있을 수 없습니다.") + String name, + + @NotNull(message = "성별 필드는 비어있을 수 없습니다.") + Boolean isMale, + + @NotNull(message = "생년월일 필드는 비어있을 수 없습니다.") + LocalDate birth, + + @NotBlank(message = "주소 필드는 비어있을 수 없습니다.") + String address, + + @NotBlank(message = "상세 주소 필드는 비어있을 수 없습니다.") + String detailAddress, + + @NotEmpty(message = "선호 음식 배열은 비어있을 수 없습니다.") + List foodList, + + @NotBlank(message = "이메일 필드는 비어있을 수 없습니다.") @Email(message = "이메일 형식이 맞지 않습니다.") + String email, + + @NotBlank(message = "비밀번호 필드는 비어있을 수 없습니다.") + String password, + + @NotBlank(message = "전화번호 필드는 비어있을 수 없습니다.") + String phoneNum +) { + + public record Agree( + Boolean age, + Boolean service, + Boolean privacy, + Boolean location, + Boolean marketing + ) {} +} From ab8b9006852d7246df99f81a70e20f1596e314f9 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:37:58 +0900 Subject: [PATCH 18/20] =?UTF-8?q?fix:=20sign-in=20=EC=84=B1=EA=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A5=BC=20sign-up=20=EC=84=B1=EA=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/study/domain/user/exception/code/UserSuccessCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Joonseok/src/main/java/com/umc/study/domain/user/exception/code/UserSuccessCode.java b/Joonseok/src/main/java/com/umc/study/domain/user/exception/code/UserSuccessCode.java index d4da4366..85042040 100644 --- a/Joonseok/src/main/java/com/umc/study/domain/user/exception/code/UserSuccessCode.java +++ b/Joonseok/src/main/java/com/umc/study/domain/user/exception/code/UserSuccessCode.java @@ -9,7 +9,7 @@ @AllArgsConstructor public enum UserSuccessCode implements BaseResponseCode { - USER_SIGN_IN_CREATED( + USER_SIGN_UP_CREATED( HttpStatus.CREATED, "USER201_1", "회원가입에 성공했습니다." From 14d56e13cada995768fe4ef24cf50eedb5997dc5 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:38:15 +0900 Subject: [PATCH 19/20] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A9=94=EC=86=8C=EB=93=9C=EC=97=90=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EB=B9=8C=EB=93=9C=ED=95=B4=EC=84=9C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=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 --- .../domain/user/service/UserService.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Joonseok/src/main/java/com/umc/study/domain/user/service/UserService.java b/Joonseok/src/main/java/com/umc/study/domain/user/service/UserService.java index de4576c7..8ca92a0a 100644 --- a/Joonseok/src/main/java/com/umc/study/domain/user/service/UserService.java +++ b/Joonseok/src/main/java/com/umc/study/domain/user/service/UserService.java @@ -1,20 +1,26 @@ package com.umc.study.domain.user.service; +import com.umc.study.domain.mission.entity.Location; import com.umc.study.domain.mission.entity.Mission; import com.umc.study.domain.mission.entity.Restaurant; import com.umc.study.domain.mission.exception.PageOverflowException; import com.umc.study.domain.mission.repository.MissionRepository; import com.umc.study.domain.mission.repository.RestaurantRepository; import com.umc.study.domain.user.entity.User; +import com.umc.study.domain.user.enums.Role; import com.umc.study.domain.user.exception.UserNotFoundException; import com.umc.study.domain.user.repository.UserRepository; import com.umc.study.domain.user.web.dto.GetHomeRes; import com.umc.study.domain.user.web.dto.GetMyPageRes; +import com.umc.study.domain.user.web.dto.SignInReq; +import com.umc.study.domain.user.web.dto.SignUpReq; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -25,6 +31,27 @@ public class UserService { private final UserRepository userRepository; private final MissionRepository missionRepository; private final RestaurantRepository restaurantRepository; + private final PasswordEncoder encoder; + + @Transactional + public void saveUser(SignUpReq request) { + User user = User.builder() + .name(request.name()) + .isMale(request.isMale()) + .birth(request.birth()) + .role(Role.GUEST) + .email(request.email()) + .password(encoder.encode(request.password())) + .phoneNum(request.phoneNum()) + .pointDeposit(0) + .location(Location.builder() + .streetAddress(request.address()) + .detailedAddress(request.detailAddress()) + .build()) + .build(); + + userRepository.save(user); + } public GetMyPageRes getMyPage(Long userId) { From 2338fabdf8d0359f655bdc418f58803a472865db Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 19 May 2026 12:38:36 +0900 Subject: [PATCH 20/20] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9A=94=EC=B2=AD=EC=97=90=20=EC=8B=A4=EC=A0=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=B9=84=EC=A6=88?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/web/controller/UserController.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java b/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java index b3885653..f3d78f78 100644 --- a/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java +++ b/Joonseok/src/main/java/com/umc/study/domain/user/web/controller/UserController.java @@ -4,6 +4,7 @@ import com.umc.study.domain.user.service.UserService; import com.umc.study.domain.user.web.dto.GetHomeRes; import com.umc.study.domain.user.web.dto.GetMyPageRes; +import com.umc.study.domain.user.web.dto.SignUpReq; import com.umc.study.global.apiPayload.ApiResponse; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; @@ -21,15 +22,17 @@ public class UserController { private final UserService userService; - @PostMapping("/auth/sign-in") - public ResponseEntity> signIn( - @Valid @RequestBody Object request + @PostMapping("/auth/sign-up") + public ResponseEntity> signUp( + @Valid @RequestBody SignUpReq request ) { // call Service method + userService.saveUser(request); + return ResponseEntity - .status(UserSuccessCode.USER_SIGN_IN_CREATED.getStatus()) - .body(ApiResponse.onComplete(UserSuccessCode.USER_SIGN_IN_CREATED, null)); + .status(UserSuccessCode.USER_SIGN_UP_CREATED.getStatus()) + .body(ApiResponse.onComplete(UserSuccessCode.USER_SIGN_UP_CREATED, null)); } @PostMapping("/my/pref")