From c58f732e1d0d8c16fc7caf7ec7883f315aa8bb00 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:45:19 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20Role=EC=97=90=20ADMIN=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20Spring=20Security=20=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/solidconnection/type/Role.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/type/Role.java b/src/main/java/com/example/solidconnection/type/Role.java index aaf464bf8..710d261d0 100644 --- a/src/main/java/com/example/solidconnection/type/Role.java +++ b/src/main/java/com/example/solidconnection/type/Role.java @@ -1,6 +1,18 @@ package com.example.solidconnection.type; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.List; + public enum Role { + + ADMIN, MENTOR, - MENTEE + MENTEE; + + private static final String ROLE_PREFIX = "ROLE_"; + + public List getAuthorities() { + return List.of(new SimpleGrantedAuthority(ROLE_PREFIX + name())); + } } From 9d3f5e7069990a11be7d14d366f2d4b08242442a Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:46:40 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20ADMIN=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=B3=B4=ED=98=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfiguration.java | 7 ++++++- .../custom/security/userdetails/SiteUserDetails.java | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index 98492568b..a549b4213 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -18,6 +18,8 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import static com.example.solidconnection.type.Role.ADMIN; + @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -55,7 +57,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .formLogin(AbstractHttpConfigurer::disable) .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/admin/**").hasRole(ADMIN.name()) + .anyRequest().permitAll() + ) .addFilterBefore(jwtAuthenticationFilter, BasicAuthenticationFilter.class) .addFilterBefore(signOutCheckFilter, JwtAuthenticationFilter.class) .addFilterBefore(exceptionHandlerFilter, SignOutCheckFilter.class) diff --git a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java b/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java index 36a0b815a..b37c25afd 100644 --- a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java +++ b/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java @@ -27,7 +27,7 @@ public String getUsername() { @Override public Collection getAuthorities() { - return null; + return siteUser.getRole().getAuthorities(); } @Override From 5e5fda909a4b30ef1e9afd5deaa645f95115c974 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:47:42 +0900 Subject: [PATCH 03/16] =?UTF-8?q?refactor:=20Security=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=EC=B2=B4=EC=9D=B8=20=EC=8B=A4=ED=96=89=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/config/security/SecurityConfiguration.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index a549b4213..6afc199de 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -13,6 +13,7 @@ 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.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -63,7 +64,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .addFilterBefore(jwtAuthenticationFilter, BasicAuthenticationFilter.class) .addFilterBefore(signOutCheckFilter, JwtAuthenticationFilter.class) - .addFilterBefore(exceptionHandlerFilter, SignOutCheckFilter.class) + .addFilterAfter(exceptionHandlerFilter, ExceptionTranslationFilter.class) .build(); } } From ed584a384946868bf50aa949871bbb7fab552ab5 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:52:59 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EC=95=88?= =?UTF-8?q?=EB=90=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EB=B0=8F=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=97=86=EB=8A=94=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/exception/ErrorCode.java | 1 + .../filter/ExceptionHandlerFilter.java | 30 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java index 529430749..8c74ea55b 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java @@ -48,6 +48,7 @@ public enum ErrorCode { AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED.value(), "인증이 필요한 접근입니다."), ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰이 만료되었습니다. 재발급 api를 호출해주세요."), REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 만료되었습니다. 다시 로그인을 진행해주세요."), + ACCESS_DENIED(HttpStatus.FORBIDDEN.value(), "접근 권한이 없습니다."), // s3 S3_SERVICE_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "S3 서비스 에러 발생"), diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java index 8d09bfada..ad150035c 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java @@ -1,6 +1,7 @@ package com.example.solidconnection.custom.security.filter; import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.custom.exception.ErrorCode; import com.example.solidconnection.custom.response.ErrorResponse; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.FilterChain; @@ -9,12 +10,16 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED; import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; @Component @@ -30,26 +35,33 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, try { filterChain.doFilter(request, response); } catch (CustomException e) { - customCommence(response, e); + customCommence(response, e, e.getCode()); + } catch (AccessDeniedException e) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth instanceof AnonymousAuthenticationToken) { + generalCommence(response, e, AUTHENTICATION_FAILED); + } else { + generalCommence(response, e, ACCESS_DENIED); + } } catch (Exception e) { - generalCommence(response, e); + generalCommence(response, e, AUTHENTICATION_FAILED); } } - public void customCommence(HttpServletResponse response, CustomException customException) throws IOException { + public void customCommence(HttpServletResponse response, CustomException customException, int statusCode) throws IOException { SecurityContextHolder.clearContext(); ErrorResponse errorResponse = new ErrorResponse(customException); - writeResponse(response, errorResponse); + writeResponse(response, errorResponse, statusCode); } - public void generalCommence(HttpServletResponse response, Exception exception) throws IOException { + public void generalCommence(HttpServletResponse response, Exception exception, ErrorCode errorCode) throws IOException { SecurityContextHolder.clearContext(); - ErrorResponse errorResponse = new ErrorResponse(AUTHENTICATION_FAILED, exception.getMessage()); - writeResponse(response, errorResponse); + ErrorResponse errorResponse = new ErrorResponse(errorCode, exception.getMessage()); + writeResponse(response, errorResponse, errorCode.getCode()); } - private void writeResponse(HttpServletResponse response, ErrorResponse errorResponse) throws IOException { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + private void writeResponse(HttpServletResponse response, ErrorResponse errorResponse, int statusCode) throws IOException { + response.setStatus(statusCode); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); From 1ac57ff304c754e4d57ae47daf5e2efd51188423 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:53:21 +0900 Subject: [PATCH 05/16] =?UTF-8?q?test:=20ExceptionHandlerFilter=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=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 --- .../filter/ExceptionHandlerFilterTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java b/src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java index 091a75eb8..fd4bd62a8 100644 --- a/src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java @@ -13,8 +13,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import java.util.stream.Stream; @@ -82,10 +85,46 @@ void setUp() { assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } + @Test + void 익명_사용자의_접근_거부시_401_예외_응답을_반환한다() throws Exception { + // given + Authentication anonymousAuth = getAnonymousAuth(); + SecurityContextHolder.getContext().setAuthentication(anonymousAuth); + willThrow(new AccessDeniedException("Access Denied")).given(filterChain).doFilter(request, response); + + // when + exceptionHandlerFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + } + + @Test + void 인증된_사용자의_접근_거부하면_403_예외_응답을_반환한다() throws Exception { + // given + Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_USER"); + SecurityContextHolder.getContext().setAuthentication(auth); + willThrow(new AccessDeniedException("Access Denied")).given(filterChain).doFilter(request, response); + + // when + exceptionHandlerFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); + } + private static Stream provideException() { return Stream.of( new RuntimeException(), new CustomException(ErrorCode.INVALID_TOKEN) ); } + + private Authentication getAnonymousAuth() { + return new AnonymousAuthenticationToken( + "key", + "anonymousUser", + AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS") + ); + } } From 5ced9e399f464914927a772bfd44718cf50c33b1 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 18:27:09 +0900 Subject: [PATCH 06/16] =?UTF-8?q?chore:=20ADMIN=20Role=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/db/migration/V6__add_admin_to_role_enum.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/resources/db/migration/V6__add_admin_to_role_enum.sql diff --git a/src/main/resources/db/migration/V6__add_admin_to_role_enum.sql b/src/main/resources/db/migration/V6__add_admin_to_role_enum.sql new file mode 100644 index 000000000..a661a2a61 --- /dev/null +++ b/src/main/resources/db/migration/V6__add_admin_to_role_enum.sql @@ -0,0 +1,2 @@ +ALTER TABLE site_user + modify ROLE enum ('MENTEE', 'MENTOR', 'ADMIN') NOT NULL; From f698ada048841d7036d7c50e9f3acafcb008c480 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 18:55:30 +0900 Subject: [PATCH 07/16] =?UTF-8?q?fix:=20JWT=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B6=8C=ED=95=9C=20=EC=A0=95=EB=B3=B4=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=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 - JwtAuthentication 생성자에서 권한 정보를 상위 클래스에 전달하도록 수정 --- .../custom/security/authentication/JwtAuthentication.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java b/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java index ba195caff..eecd810cd 100644 --- a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java +++ b/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java @@ -1,6 +1,9 @@ package com.example.solidconnection.custom.security.authentication; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collections; public abstract class JwtAuthentication extends AbstractAuthenticationToken { @@ -9,7 +12,7 @@ public abstract class JwtAuthentication extends AbstractAuthenticationToken { private final Object principal; public JwtAuthentication(String token, Object principal) { - super(null); + super(principal != null ? ((UserDetails)principal).getAuthorities() : Collections.emptyList()); this.credentials = token; this.principal = principal; } From dbca920d4ef04b36331ba0b9abad0c7250c0a78e Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 9 Feb 2025 18:59:32 +0900 Subject: [PATCH 08/16] =?UTF-8?q?fix:=20=EB=A7=8C=EB=A3=8C=EB=90=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=B2=98=EB=A6=AC=20=EC=8B=9C=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EB=B0=9C=EC=83=9D=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 - JwtAuthentication 생성자에서 권한 정보 처리 로직 수정 --- .../custom/security/authentication/JwtAuthentication.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java b/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java index eecd810cd..ab94069ba 100644 --- a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java +++ b/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java @@ -12,7 +12,9 @@ public abstract class JwtAuthentication extends AbstractAuthenticationToken { private final Object principal; public JwtAuthentication(String token, Object principal) { - super(principal != null ? ((UserDetails)principal).getAuthorities() : Collections.emptyList()); + super(principal instanceof UserDetails ? + ((UserDetails)principal).getAuthorities() : + Collections.emptyList()); this.credentials = token; this.principal = principal; } From df11c1d5699272214dd60fdb4c71207c316a4f52 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:38:59 +0900 Subject: [PATCH 09/16] =?UTF-8?q?refactor:=20customException=EB=A5=BC=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EA=B0=84=EA=B2=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/security/filter/ExceptionHandlerFilter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java index ad150035c..80c0b9e0f 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java @@ -35,7 +35,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, try { filterChain.doFilter(request, response); } catch (CustomException e) { - customCommence(response, e, e.getCode()); + customCommence(response, e); } catch (AccessDeniedException e) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth instanceof AnonymousAuthenticationToken) { @@ -48,10 +48,10 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, } } - public void customCommence(HttpServletResponse response, CustomException customException, int statusCode) throws IOException { + public void customCommence(HttpServletResponse response, CustomException customException) throws IOException { SecurityContextHolder.clearContext(); ErrorResponse errorResponse = new ErrorResponse(customException); - writeResponse(response, errorResponse, statusCode); + writeResponse(response, errorResponse, customException.getCode()); } public void generalCommence(HttpServletResponse response, Exception exception, ErrorCode errorCode) throws IOException { From a1b98d107658f5c43c5c5bcbf49ec648395651f4 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:40:34 +0900 Subject: [PATCH 10/16] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A4=91=EB=B3=B5=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/security/filter/ExceptionHandlerFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java index 80c0b9e0f..d98f021a5 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java @@ -49,18 +49,17 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, } public void customCommence(HttpServletResponse response, CustomException customException) throws IOException { - SecurityContextHolder.clearContext(); ErrorResponse errorResponse = new ErrorResponse(customException); writeResponse(response, errorResponse, customException.getCode()); } public void generalCommence(HttpServletResponse response, Exception exception, ErrorCode errorCode) throws IOException { - SecurityContextHolder.clearContext(); ErrorResponse errorResponse = new ErrorResponse(errorCode, exception.getMessage()); writeResponse(response, errorResponse, errorCode.getCode()); } private void writeResponse(HttpServletResponse response, ErrorResponse errorResponse, int statusCode) throws IOException { + SecurityContextHolder.clearContext(); response.setStatus(statusCode); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); From b0b78cc740adfd2e259c3abaeaf7fa0468d7bdc7 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:44:00 +0900 Subject: [PATCH 11/16] =?UTF-8?q?refactor:=20=EC=82=BC=ED=95=AD=20?= =?UTF-8?q?=EC=97=B0=EC=82=B0=EC=9E=90=EB=A1=9C=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=BD=94=EB=93=9C=20=EA=B0=84=EA=B2=B0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/security/filter/ExceptionHandlerFilter.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java index d98f021a5..1b8fac2bb 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java @@ -38,11 +38,8 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, customCommence(response, e); } catch (AccessDeniedException e) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth instanceof AnonymousAuthenticationToken) { - generalCommence(response, e, AUTHENTICATION_FAILED); - } else { - generalCommence(response, e, ACCESS_DENIED); - } + ErrorCode errorCode = auth instanceof AnonymousAuthenticationToken ? AUTHENTICATION_FAILED : ACCESS_DENIED; + generalCommence(response, e, errorCode); } catch (Exception e) { generalCommence(response, e, AUTHENTICATION_FAILED); } From cf2c2d6edb6ef788511673133bbe5a58dbff16d0 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:46:44 +0900 Subject: [PATCH 12/16] =?UTF-8?q?style:=20=ED=98=95=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=20=EC=8B=9C=20=EA=B3=B5=EB=B0=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/security/authentication/JwtAuthentication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java b/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java index ab94069ba..6c9f2fa21 100644 --- a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java +++ b/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java @@ -13,7 +13,7 @@ public abstract class JwtAuthentication extends AbstractAuthenticationToken { public JwtAuthentication(String token, Object principal) { super(principal instanceof UserDetails ? - ((UserDetails)principal).getAuthorities() : + ((UserDetails) principal).getAuthorities() : Collections.emptyList()); this.credentials = token; this.principal = principal; From b04de5ff7d0a0eb377faa2961c574f9c23834c43 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:17:16 +0900 Subject: [PATCH 13/16] =?UTF-8?q?refactor:=20Role=20enum=EC=9D=84=20?= =?UTF-8?q?=EC=88=9C=EC=88=98=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/mapper/SecurityRoleMapper.java | 18 ++++++++++++++++++ .../security/userdetails/SiteUserDetails.java | 3 ++- .../com/example/solidconnection/type/Role.java | 10 ---------- 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/custom/security/mapper/SecurityRoleMapper.java diff --git a/src/main/java/com/example/solidconnection/custom/security/mapper/SecurityRoleMapper.java b/src/main/java/com/example/solidconnection/custom/security/mapper/SecurityRoleMapper.java new file mode 100644 index 000000000..08002cc74 --- /dev/null +++ b/src/main/java/com/example/solidconnection/custom/security/mapper/SecurityRoleMapper.java @@ -0,0 +1,18 @@ +package com.example.solidconnection.custom.security.mapper; + +import com.example.solidconnection.type.Role; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.List; + +public class SecurityRoleMapper { + + private static final String ROLE_PREFIX = "ROLE_"; + + private SecurityRoleMapper() { + } + + public static List mapRoleToAuthorities(Role role) { + return List.of(new SimpleGrantedAuthority(ROLE_PREFIX + role.name())); + } +} diff --git a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java b/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java index b37c25afd..0f5f7a6d2 100644 --- a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java +++ b/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java @@ -1,5 +1,6 @@ package com.example.solidconnection.custom.security.userdetails; +import com.example.solidconnection.custom.security.mapper.SecurityRoleMapper; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; @@ -27,7 +28,7 @@ public String getUsername() { @Override public Collection getAuthorities() { - return siteUser.getRole().getAuthorities(); + return SecurityRoleMapper.mapRoleToAuthorities(siteUser.getRole()); } @Override diff --git a/src/main/java/com/example/solidconnection/type/Role.java b/src/main/java/com/example/solidconnection/type/Role.java index 710d261d0..8223e8de0 100644 --- a/src/main/java/com/example/solidconnection/type/Role.java +++ b/src/main/java/com/example/solidconnection/type/Role.java @@ -1,18 +1,8 @@ package com.example.solidconnection.type; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import java.util.List; - public enum Role { ADMIN, MENTOR, MENTEE; - - private static final String ROLE_PREFIX = "ROLE_"; - - public List getAuthorities() { - return List.of(new SimpleGrantedAuthority(ROLE_PREFIX + name())); - } } From 719762e11b34e1e5717230fc9694b0684dcc66c3 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:17:27 +0900 Subject: [PATCH 14/16] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=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 --- .../userdetails/SiteUserDetailsServiceTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java index 731f840f3..004cbafa2 100644 --- a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java @@ -46,6 +46,21 @@ class SiteUserDetailsServiceTest { ); } + @Test + void 사용자_권한_정보를_정상적으로_반환한다() { + // given + SiteUser siteUser = siteUserRepository.save(createSiteUser()); + String username = getUserName(siteUser); + + // when + SiteUserDetails userDetails = (SiteUserDetails) userDetailsService.loadUserByUsername(username); + + // then + assertThat(userDetails.getAuthorities()) + .extracting("authority") + .containsExactly("ROLE_" + siteUser.getRole().name()); + } + @Nested class 예외_응답을_반환한다 { From c7bc0fd87f620e070a30f0a4be2d0faabeff25df Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:45:56 +0900 Subject: [PATCH 15/16] =?UTF-8?q?refactor:=20=EB=A7=A4=ED=8D=BC=20security?= =?UTF-8?q?.userdetails=20=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/{mapper => userdetails}/SecurityRoleMapper.java | 2 +- .../custom/security/userdetails/SiteUserDetails.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) rename src/main/java/com/example/solidconnection/custom/security/{mapper => userdetails}/SecurityRoleMapper.java (87%) diff --git a/src/main/java/com/example/solidconnection/custom/security/mapper/SecurityRoleMapper.java b/src/main/java/com/example/solidconnection/custom/security/userdetails/SecurityRoleMapper.java similarity index 87% rename from src/main/java/com/example/solidconnection/custom/security/mapper/SecurityRoleMapper.java rename to src/main/java/com/example/solidconnection/custom/security/userdetails/SecurityRoleMapper.java index 08002cc74..3af238f13 100644 --- a/src/main/java/com/example/solidconnection/custom/security/mapper/SecurityRoleMapper.java +++ b/src/main/java/com/example/solidconnection/custom/security/userdetails/SecurityRoleMapper.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.security.mapper; +package com.example.solidconnection.custom.security.userdetails; import com.example.solidconnection.type.Role; import org.springframework.security.core.authority.SimpleGrantedAuthority; diff --git a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java b/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java index 0f5f7a6d2..008f77ef5 100644 --- a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java +++ b/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java @@ -1,6 +1,5 @@ package com.example.solidconnection.custom.security.userdetails; -import com.example.solidconnection.custom.security.mapper.SecurityRoleMapper; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; From 34970ce3e6a7edf178edf5965fb3864ec1193336 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 <126947828+Gyuhyeok99@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:50:12 +0900 Subject: [PATCH 16/16] =?UTF-8?q?test:=20SiteUserDetailsTest=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EA=B6=8C=ED=95=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SiteUserDetailsServiceTest.java | 17 +------ .../userdetails/SiteUserDetailsTest.java | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java diff --git a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java index 004cbafa2..99e463955 100644 --- a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java @@ -18,7 +18,7 @@ import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; @DisplayName("사용자 인증 정보 서비스 테스트") @TestContainerSpringBootTest @@ -46,21 +46,6 @@ class SiteUserDetailsServiceTest { ); } - @Test - void 사용자_권한_정보를_정상적으로_반환한다() { - // given - SiteUser siteUser = siteUserRepository.save(createSiteUser()); - String username = getUserName(siteUser); - - // when - SiteUserDetails userDetails = (SiteUserDetails) userDetailsService.loadUserByUsername(username); - - // then - assertThat(userDetails.getAuthorities()) - .extracting("authority") - .containsExactly("ROLE_" + siteUser.getRole().name()); - } - @Nested class 예외_응답을_반환한다 { diff --git a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java new file mode 100644 index 000000000..912072d2b --- /dev/null +++ b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java @@ -0,0 +1,51 @@ +package com.example.solidconnection.custom.security.userdetails; + +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.type.Gender; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("사용자 인증 정보 테스트") +@TestContainerSpringBootTest +class SiteUserDetailsTest { + + @Autowired + private SiteUserRepository siteUserRepository; + + @Test + void 사용자_권한을_정상적으로_반환한다() { + // given + SiteUser siteUser = siteUserRepository.save(createSiteUser()); + SiteUserDetails siteUserDetails = new SiteUserDetails(siteUser); + + // when + Collection authorities = siteUserDetails.getAuthorities(); + + // then + assertThat(authorities) + .extracting("authority") + .containsExactly("ROLE_" + siteUser.getRole().name()); + } + + private SiteUser createSiteUser() { + return new SiteUser( + "test@example.com", + "nickname", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.MALE + ); + } +}