- SSL
- java
- sql
- 데이터베이스
- AWS
- ORM
- spring boot
- PYTHON
- select
- mysql
- springboot
- static
- 자바
- 스프링부트
- join
- 프로그래머스
- Docker
- nginx
- spring mvc
- spring
- spring security 6
- 스프링
- 1차원 배열
- jpa
- Django
- hibernate
- DI
- string
- @transactional
- 문자열
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
개발하는 자몽
[Spring Security] 예외 처리하기 본문
Notice Spring Boot 3.2.5, Spring Security 6.2.5 기반으로 작성한 글입니다.
아래 포스팅들을 먼저 보면 좋습니다.
- Spring Security 6 - Architecture → Security Exception 처리하기
- [Spring Security] 토큰 기반 로그인/로그아웃 구현하기
이 글의 목표는 Spring Security에서 토큰 기반 인증을 진행할 때 발생한 예외를
시큐리티 예외 처리 구현
Spring Security에서 예외 처리는 다음과 같은 아키텍처로 이루어진다.
- FilterChain
Spring Security는FilterChain 으로 구성되어 있으며, 각 필터는 요청을 처리하거나 다음 필터로 전달한다. 예외가 발생하면 예외 처리 필터가 작동한다. - ExceptionTranslationFilter
해당 필터는 예외를 Spring Security 예외로 변환하고, 적절한AuthenticationEntryPoint 또는AccessDeniedHandler 를 호출한다. 이 필터는 이 둘을 주입받아 사용한다. - AuthenticationEntryPoint
AuthenticationEntryPoint 는 인증되지 않은 요청에 대한 처리를 담당한다. 일반적으로 로그인 페이지로 리디렉션 하거나 401 Unauthorized 응답을 반환한다. - AccessDeniedHandler
AccessDeniedHandler 는 인가되지 않은 요청(권한 부족)에 대한 처리를 담당한다. 일반적으로 403 Forbidden 응답을 반환한다.
따라서 Spring Security 예외 처리 흐름은 다음과 같다.
- 요청이 들어온다.
- FilterChain에서 예외가 발생한다.
- ExceptionTranslationFilter가 예외를 캐치하고, Spring Security 예외로 변환한다.
- AuthenticationEntryPoint 또는 AccessDeniedHandler가 호출되어 예외를 처리한다.
위 과정처럼 Spring Security는 요청이 컨트롤러에 도달하기 전에
이를 원하는 대로 처리하려면 예외를 인터셉터하는
SecurityConfig
...
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
...
private final CustomAuthenticationEntryPoint authenticationEntryPoint;
private final CustomAccessDeniedHandler accessDeniedHandler;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
...
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(e -> e
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
)
...
;
return http.build();
}
}
Custom AuthenticationEntryPoint
...
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final HandlerExceptionResolver resolver;
public CustomAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
@Override
public void commence(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull AuthenticationException authException) {
resolver.resolveException(request, response, null, authException);
}
}
Custom AccessDeniedHandler
...
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
private final HandlerExceptionResolver resolver;
public CustomAccessDeniedHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
resolver.resolveException(request, response, null, accessDeniedException);
}
}
HandlerExceptionResovler
Spring MVC에서
따라서 각 커스텀 구현한
HandlerExceptionResolver.resolveException(request, response, null, accessDeniedException);
참고로 위 코드처럼
우리는
Custom ExceptionHandler
이곳에서 Spring Security 예외를 처리한다. 보다 깔끔하게 예외를 가공하고 처리하기 위해서 컨트롤러 계층까지 예외를 가져왔다.
...
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public ResponseEntity<ExceptionResponseDto> handleAuthenticationException(AuthenticationException e) {
ExceptionResponseDto dto = new ExceptionResponseDto(HttpStatus.UNAUTHORIZED.name(), ExceptionCode.INVALID_TOKEN.getMessage());
HttpStatus httpStatus = HttpStatus.UNAUTHORIZED;
return ResponseEntity.status(httpStatus).body(dto);
}
@ExceptionHandler
public ResponseEntity<ExceptionResponseDto> handleAccessDeniedException(AccessDeniedException e) {
ExceptionResponseDto dto = new ExceptionResponseDto(HttpStatus.FORBIDDEN.name(), ExceptionCode.ACCESS_DENIED.getMessage());
HttpStatus httpStatus = HttpStatus.FORBIDDEN;
return ResponseEntity.status(httpStatus).body(dto);
}
...
}
클라이언트에게 예외를 반환할 때 필요한 정보만 담을 수 있도록 별도로 DTO를 생성했다.
🔖참고
'Java & Kotlin > Spring' 카테고리의 다른 글
[JPA] 임베디드 타입(@Embeddable, @Embedded)에 관하여 (0) | 2024.08.23 |
---|---|
[Spring Boot / Error] Required request body is missing (0) | 2024.07.13 |
[Spring Security] 역할/권한 구현하기 (0) | 2024.06.14 |
[Spring Security] 토큰 기반 로그인/로그아웃 구현하기 (0) | 2024.06.08 |
Spring Security 6 - Architecture (1) | 2024.06.07 |