Skip to content

Commit fc17f6c

Browse files
authored
Merge pull request #311 from let-s-record-it/bug-310
Authorization Header가 없는 경우에 대한 예외처리 EntryPoint 구현
2 parents a8fba4a + d43ecd5 commit fc17f6c

5 files changed

Lines changed: 98 additions & 10 deletions

File tree

src/main/java/com/sillim/recordit/config/security/SecurityConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.sillim.recordit.config.security.filter.AuthExceptionTranslationFilter;
44
import com.sillim.recordit.config.security.filter.JwtAuthenticationFilter;
5+
import com.sillim.recordit.config.security.handler.JwtAuthenticationEntryPoint;
56
import java.util.List;
67
import lombok.RequiredArgsConstructor;
78
import org.springframework.beans.factory.annotation.Value;
@@ -34,6 +35,7 @@
3435
public class SecurityConfig {
3536

3637
private final JwtAuthenticationFilter jwtAuthenticationFilter;
38+
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
3739
private final AuthExceptionTranslationFilter authExceptionTranslationFilter;
3840
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService;
3941
private final AuthenticationSuccessHandler successHandler;
@@ -76,6 +78,8 @@ public SecurityFilterChain securityFilterChain(
7678
.sessionManagement(
7779
configurer ->
7880
configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
81+
.exceptionHandling(
82+
config -> config.authenticationEntryPoint(jwtAuthenticationEntryPoint))
7983
.addFilterBefore(
8084
jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
8185
.addFilterBefore(authExceptionTranslationFilter, JwtAuthenticationFilter.class)

src/main/java/com/sillim/recordit/config/security/handler/AuthenticationExceptionHandler.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.sillim.recordit.global.dto.response.ErrorResponse;
5+
import com.sillim.recordit.global.exception.ErrorCode;
56
import com.sillim.recordit.global.exception.common.ApplicationException;
67
import jakarta.servlet.http.HttpServletResponse;
78
import java.io.IOException;
@@ -11,6 +12,9 @@
1112
import org.springframework.http.HttpStatus;
1213
import org.springframework.http.MediaType;
1314
import org.springframework.http.ResponseEntity;
15+
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
16+
import org.springframework.security.authentication.InsufficientAuthenticationException;
17+
import org.springframework.security.core.AuthenticationException;
1418
import org.springframework.stereotype.Component;
1519

1620
@Slf4j
@@ -25,20 +29,41 @@ public void handle(HttpServletResponse response, ApplicationException exception)
2529
if (response.isCommitted()) {
2630
return;
2731
}
32+
ErrorResponse body = ErrorResponse.from(exception.getErrorCode());
33+
writeUnauthorizedResponse(response, body);
34+
}
35+
36+
public void handle(HttpServletResponse response, AuthenticationException exception)
37+
throws IOException {
38+
if (response.isCommitted()) {
39+
return;
40+
}
41+
ErrorResponse body = ErrorResponse.from(resolveErrorCode(exception));
42+
writeUnauthorizedResponse(response, body);
43+
}
44+
45+
private void writeUnauthorizedResponse(HttpServletResponse response, ErrorResponse body)
46+
throws IOException {
47+
log.info("Authentication Exception: {} {}", body.errorCode(), body.message());
2848

29-
log.info(
30-
"Authentication Exception: {} {}",
31-
exception.getErrorCode(),
32-
exception.getMessage());
3349
response.setStatus(HttpStatus.UNAUTHORIZED.value());
3450
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
3551
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
3652

3753
response.getWriter()
3854
.write(
3955
objectMapper.writeValueAsString(
40-
ResponseEntity.status(HttpStatus.UNAUTHORIZED.value())
41-
.body(ErrorResponse.from(exception.getErrorCode()))));
56+
ResponseEntity.status(HttpStatus.UNAUTHORIZED.value()).body(body)));
4257
response.getWriter().flush();
4358
}
59+
60+
private ErrorCode resolveErrorCode(AuthenticationException e) {
61+
log.debug("Implementation of AuthenticationException is {}", e.getClass().getSimpleName());
62+
if (e instanceof AuthenticationCredentialsNotFoundException
63+
|| e instanceof InsufficientAuthenticationException) {
64+
return ErrorCode.AUTHENTICATION_REQUIRED;
65+
} else {
66+
return ErrorCode.AUTHENTICATION_FAILED;
67+
}
68+
}
4469
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.sillim.recordit.config.security.handler;
2+
3+
import jakarta.servlet.http.HttpServletRequest;
4+
import jakarta.servlet.http.HttpServletResponse;
5+
import java.io.IOException;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.security.core.AuthenticationException;
8+
import org.springframework.security.web.AuthenticationEntryPoint;
9+
import org.springframework.stereotype.Component;
10+
11+
@Component
12+
@RequiredArgsConstructor
13+
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
14+
15+
private final AuthenticationExceptionHandler authenticationExceptionHandler;
16+
17+
@Override
18+
public void commence(
19+
HttpServletRequest request,
20+
HttpServletResponse response,
21+
AuthenticationException authException)
22+
throws IOException {
23+
24+
authenticationExceptionHandler.handle(response, authException);
25+
}
26+
}

src/main/java/com/sillim/recordit/global/exception/ErrorCode.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public enum ErrorCode {
1616
ID_TOKEN_INVALID_KEY("ERR_OIDC_003", "App Key가 유효하지 않습니다."),
1717
ID_TOKEN_INVALID_SIGNATURE("ERR_OIDC_004", "ID Token의 Signature가 유효하지 않습니다."),
1818

19+
AUTHENTICATION_REQUIRED("ERR_AUTH_001", "해당 리소스에 접근하기 위한 인증이 필요합니다."),
20+
AUTHENTICATION_FAILED("ERR_AUTH_999", "인증에 실패했습니다."),
21+
1922
JWT_MALFORMED("ERR_JWT_001", "JWT가 손상되었습니다."),
2023
JWT_EXPIRED("ERR_JWT_002", "JWT가 만료되었습니다."),
2124
JWT_UNSUPPORTED("ERR_JWT_003", "지원되지 않는 JWT 형식입니다."),

src/test/java/com/sillim/recordit/config/security/handler/AuthenticationExceptionHandlerTest.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99
import org.junit.jupiter.api.DisplayName;
1010
import org.junit.jupiter.api.Test;
1111
import org.springframework.mock.web.MockHttpServletResponse;
12+
import org.springframework.security.authentication.InsufficientAuthenticationException;
13+
import org.springframework.security.core.AuthenticationException;
1214

1315
class AuthenticationExceptionHandlerTest {
1416

1517
AuthenticationExceptionHandler authenticationExceptionHandler =
1618
new AuthenticationExceptionHandler(new ObjectMapper());
1719

1820
@Test
19-
@DisplayName("exception을 response를 통해 출력한다.")
20-
void responseException() throws IOException {
21+
@DisplayName("ApplicationException을 response를 통해 출력한다.")
22+
void responseApplicationException() throws IOException {
2123
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
2224
ApplicationException exception = new ApplicationException(ErrorCode.UNHANDLED_EXCEPTION);
2325

@@ -30,8 +32,36 @@ void responseException() throws IOException {
3032
}
3133

3234
@Test
33-
@DisplayName("response가 commit되어 있다면 출력되지 않는다.")
34-
void notWriteIfResponseCommitted() throws IOException {
35+
@DisplayName("AuthenticationException을 response를 통해 출력한다.")
36+
void responseAuthenticationException() throws IOException {
37+
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
38+
AuthenticationException exception =
39+
new InsufficientAuthenticationException("인증이 필요한 URI입니다.");
40+
41+
authenticationExceptionHandler.handle(httpServletResponse, exception);
42+
43+
assertThat(httpServletResponse.getStatus()).isEqualTo(401);
44+
assertThat(httpServletResponse.getContentType())
45+
.isEqualTo("application/json;charset=UTF-8");
46+
assertThat(httpServletResponse.getCharacterEncoding()).isEqualTo("UTF-8");
47+
}
48+
49+
@Test
50+
@DisplayName("ApplicationException 처리 시 response가 commit되어 있다면 출력되지 않는다.")
51+
void notWriteIfApplicationExceptionResponseCommitted() throws IOException {
52+
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
53+
AuthenticationException exception =
54+
new InsufficientAuthenticationException("인증이 필요한 URI입니다.");
55+
httpServletResponse.setCommitted(true);
56+
57+
authenticationExceptionHandler.handle(httpServletResponse, exception);
58+
59+
assertThat(httpServletResponse.getStatus()).isEqualTo(200);
60+
}
61+
62+
@Test
63+
@DisplayName("AuthenticationException 처리 시 response가 commit되어 있다면 출력되지 않는다.")
64+
void notWriteIfAuthenticationExceptionResponseCommitted() throws IOException {
3565
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
3666
ApplicationException exception = new ApplicationException(ErrorCode.UNHANDLED_EXCEPTION);
3767
httpServletResponse.setCommitted(true);

0 commit comments

Comments
 (0)