Skip to content

Commit 1039236

Browse files
authored
Merge pull request #214 from solid-connection/develop
[RELEASE] 250215 릴리즈
2 parents 90a0db7 + e990385 commit 1039236

File tree

8 files changed

+178
-38
lines changed

8 files changed

+178
-38
lines changed

src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.example.solidconnection.config.security;
22

3+
import com.example.solidconnection.custom.exception.CustomAccessDeniedHandler;
4+
import com.example.solidconnection.custom.exception.CustomAuthenticationEntryPoint;
35
import com.example.solidconnection.custom.security.filter.ExceptionHandlerFilter;
46
import com.example.solidconnection.custom.security.filter.JwtAuthenticationFilter;
57
import com.example.solidconnection.custom.security.filter.SignOutCheckFilter;
@@ -13,7 +15,6 @@
1315
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1416
import org.springframework.security.crypto.password.PasswordEncoder;
1517
import org.springframework.security.web.SecurityFilterChain;
16-
import org.springframework.security.web.access.ExceptionTranslationFilter;
1718
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
1819
import org.springframework.web.cors.CorsConfiguration;
1920
import org.springframework.web.cors.CorsConfigurationSource;
@@ -30,6 +31,8 @@ public class SecurityConfiguration {
3031
private final ExceptionHandlerFilter exceptionHandlerFilter;
3132
private final SignOutCheckFilter signOutCheckFilter;
3233
private final JwtAuthenticationFilter jwtAuthenticationFilter;
34+
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
35+
private final CustomAccessDeniedHandler customAccessDeniedHandler;
3336

3437
@Bean
3538
public CorsConfigurationSource corsConfigurationSource() {
@@ -62,9 +65,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
6265
.requestMatchers("/admin/**").hasRole(ADMIN.name())
6366
.anyRequest().permitAll()
6467
)
68+
.exceptionHandling(exception -> exception
69+
.authenticationEntryPoint(customAuthenticationEntryPoint)
70+
.accessDeniedHandler(customAccessDeniedHandler)
71+
)
6572
.addFilterBefore(jwtAuthenticationFilter, BasicAuthenticationFilter.class)
6673
.addFilterBefore(signOutCheckFilter, JwtAuthenticationFilter.class)
67-
.addFilterAfter(exceptionHandlerFilter, ExceptionTranslationFilter.class)
74+
.addFilterBefore(exceptionHandlerFilter, SignOutCheckFilter.class)
6875
.build();
6976
}
7077
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.example.solidconnection.custom.exception;
2+
3+
import com.example.solidconnection.custom.response.ErrorResponse;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import jakarta.servlet.http.HttpServletRequest;
6+
import jakarta.servlet.http.HttpServletResponse;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.security.access.AccessDeniedException;
9+
import org.springframework.security.web.access.AccessDeniedHandler;
10+
import org.springframework.stereotype.Component;
11+
12+
import java.io.IOException;
13+
14+
@Component
15+
@RequiredArgsConstructor
16+
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
17+
18+
private final ObjectMapper objectMapper;
19+
20+
@Override
21+
public void handle(HttpServletRequest request,
22+
HttpServletResponse response,
23+
AccessDeniedException accessDeniedException) throws IOException {
24+
ErrorResponse errorResponse =
25+
new ErrorResponse(ErrorCode.ACCESS_DENIED);
26+
response.setStatus(ErrorCode.ACCESS_DENIED.getCode());
27+
response.setContentType("application/json");
28+
response.setCharacterEncoding("UTF-8");
29+
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
30+
}
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.example.solidconnection.custom.exception;
2+
3+
import com.example.solidconnection.custom.response.ErrorResponse;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import jakarta.servlet.http.HttpServletRequest;
6+
import jakarta.servlet.http.HttpServletResponse;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.security.core.AuthenticationException;
9+
import org.springframework.security.web.AuthenticationEntryPoint;
10+
import org.springframework.stereotype.Component;
11+
12+
import java.io.IOException;
13+
14+
@Component
15+
@RequiredArgsConstructor
16+
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
17+
18+
private final ObjectMapper objectMapper;
19+
20+
@Override
21+
public void commence(HttpServletRequest request,
22+
HttpServletResponse response,
23+
AuthenticationException authException) throws IOException {
24+
ErrorResponse errorResponse =
25+
new ErrorResponse(ErrorCode.AUTHENTICATION_FAILED);
26+
response.setStatus(ErrorCode.AUTHENTICATION_FAILED.getCode());
27+
response.setContentType("application/json");
28+
response.setCharacterEncoding("UTF-8");
29+
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
30+
}
31+
}

src/main/java/com/example/solidconnection/custom/response/ErrorResponse.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ public ErrorResponse(CustomException e) {
99
this(e.getMessage());
1010
}
1111

12+
public ErrorResponse(ErrorCode e) {
13+
this(e.getMessage());
14+
}
15+
1216
public ErrorResponse(ErrorCode e, String detail) {
1317
this(e.getMessage() + " : " + detail);
1418
}

src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,12 @@
1010
import jakarta.servlet.http.HttpServletResponse;
1111
import lombok.NonNull;
1212
import lombok.RequiredArgsConstructor;
13-
import org.springframework.security.access.AccessDeniedException;
14-
import org.springframework.security.authentication.AnonymousAuthenticationToken;
15-
import org.springframework.security.core.Authentication;
1613
import org.springframework.security.core.context.SecurityContextHolder;
1714
import org.springframework.stereotype.Component;
1815
import org.springframework.web.filter.OncePerRequestFilter;
1916

2017
import java.io.IOException;
2118

22-
import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED;
2319
import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED;
2420

2521
@Component
@@ -36,10 +32,6 @@ protected void doFilterInternal(@NonNull HttpServletRequest request,
3632
filterChain.doFilter(request, response);
3733
} catch (CustomException e) {
3834
customCommence(response, e);
39-
} catch (AccessDeniedException e) {
40-
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
41-
ErrorCode errorCode = auth instanceof AnonymousAuthenticationToken ? AUTHENTICATION_FAILED : ACCESS_DENIED;
42-
generalCommence(response, e, errorCode);
4335
} catch (Exception e) {
4436
generalCommence(response, e, AUTHENTICATION_FAILED);
4537
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.example.solidconnection.custom.exception;
2+
3+
import com.example.solidconnection.custom.response.ErrorResponse;
4+
import com.example.solidconnection.support.TestContainerSpringBootTest;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.mock.web.MockHttpServletRequest;
11+
import org.springframework.mock.web.MockHttpServletResponse;
12+
import org.springframework.security.access.AccessDeniedException;
13+
14+
import java.io.IOException;
15+
16+
import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED;
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
@TestContainerSpringBootTest
20+
@DisplayName("커스텀 인가 예외 처리 테스트")
21+
class CustomAccessDeniedHandlerTest {
22+
23+
@Autowired
24+
private CustomAccessDeniedHandler accessDeniedHandler;
25+
26+
@Autowired
27+
private ObjectMapper objectMapper;
28+
29+
private MockHttpServletRequest request;
30+
private MockHttpServletResponse response;
31+
32+
@BeforeEach
33+
void setUp() {
34+
request = new MockHttpServletRequest();
35+
response = new MockHttpServletResponse();
36+
}
37+
38+
@Test
39+
void 권한이_없는_사용자_접근시_403_예외_응답을_반환한다() throws IOException {
40+
// given
41+
AccessDeniedException accessDeniedException = new AccessDeniedException(ACCESS_DENIED.getMessage());
42+
43+
// when
44+
accessDeniedHandler.handle(request, response, accessDeniedException);
45+
46+
// then
47+
ErrorResponse errorResponse = objectMapper.readValue(response.getContentAsString(), ErrorResponse.class);
48+
assertThat(response.getStatus()).isEqualTo(ACCESS_DENIED.getCode());
49+
assertThat(errorResponse.message()).isEqualTo(ACCESS_DENIED.getMessage());
50+
}
51+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.example.solidconnection.custom.exception;
2+
3+
import com.example.solidconnection.custom.response.ErrorResponse;
4+
import com.example.solidconnection.support.TestContainerSpringBootTest;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.mock.web.MockHttpServletRequest;
11+
import org.springframework.mock.web.MockHttpServletResponse;
12+
import org.springframework.security.authentication.AuthenticationServiceException;
13+
import org.springframework.security.core.AuthenticationException;
14+
15+
import java.io.IOException;
16+
17+
import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
@TestContainerSpringBootTest
21+
@DisplayName("커스텀 인증 예외 처리 테스트")
22+
class CustomAuthenticationEntryPointTest {
23+
24+
@Autowired
25+
private CustomAuthenticationEntryPoint authenticationEntryPoint;
26+
27+
@Autowired
28+
private ObjectMapper objectMapper;
29+
30+
private MockHttpServletRequest request;
31+
private MockHttpServletResponse response;
32+
33+
@BeforeEach
34+
void setUp() {
35+
request = new MockHttpServletRequest();
36+
response = new MockHttpServletResponse();
37+
}
38+
39+
@Test
40+
void 인증되지_않은_사용자_접근시_401_예외_응답을_반환한다() throws IOException {
41+
// given
42+
AuthenticationException authException = new AuthenticationServiceException(AUTHENTICATION_FAILED.getMessage());
43+
44+
// when
45+
authenticationEntryPoint.commence(request, response, authException);
46+
47+
// then
48+
ErrorResponse errorResponse = objectMapper.readValue(response.getContentAsString(), ErrorResponse.class);
49+
assertThat(response.getStatus()).isEqualTo(AUTHENTICATION_FAILED.getCode());
50+
assertThat(errorResponse.message()).isEqualTo(AUTHENTICATION_FAILED.getMessage());
51+
}
52+
}

src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -85,34 +85,6 @@ void setUp() {
8585
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
8686
}
8787

88-
@Test
89-
void 익명_사용자의_접근_거부시_401_예외_응답을_반환한다() throws Exception {
90-
// given
91-
Authentication anonymousAuth = getAnonymousAuth();
92-
SecurityContextHolder.getContext().setAuthentication(anonymousAuth);
93-
willThrow(new AccessDeniedException("Access Denied")).given(filterChain).doFilter(request, response);
94-
95-
// when
96-
exceptionHandlerFilter.doFilterInternal(request, response, filterChain);
97-
98-
// then
99-
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
100-
}
101-
102-
@Test
103-
void 인증된_사용자의_접근_거부하면_403_예외_응답을_반환한다() throws Exception {
104-
// given
105-
Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_USER");
106-
SecurityContextHolder.getContext().setAuthentication(auth);
107-
willThrow(new AccessDeniedException("Access Denied")).given(filterChain).doFilter(request, response);
108-
109-
// when
110-
exceptionHandlerFilter.doFilterInternal(request, response, filterChain);
111-
112-
// then
113-
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
114-
}
115-
11688
private static Stream<Throwable> provideException() {
11789
return Stream.of(
11890
new RuntimeException(),

0 commit comments

Comments
 (0)