Service
에서 반환 타입을 ResponseEntity
로 해서 글 작성자가 아닌 사용자가 해당 글을 수정하려고 할 때 403
에러를 응답하는 코드를 사용했다.
하지만 Service
에서는 ResponseEntity
로 반환을 하게 되면 비즈니스 로직을 처리할 뿐만 아니라 http status code
도 응답하는 2가지 역할을 하고 있던 것이다. 이는 객체 지향 설계 원칙(SOLID)
에서 SRP
를 위배하는 행위이고, 확장성
으로 봤을 때도 좋지 않기 때문에 OCP
도 위반했다고 볼 수 있다.
그래서 ResponseEntity
를 바로 뺐다.
기존에 사용했던 return new ResponseEntity<>(...);
부분을 throw new AccessDeniedException(...);
로 변경했다.
여기서 AccessDeniedException
은 스프링 시큐리티
에서 제공하는 Exception
이다.
처음에는 AccessDeniedException
대신에 커스텀으로 Exception
을 하나 만들고 Global Exception Handler
를 생성해서 해당 Exception
이 throw
되었을 때 Global Exception Handler
를 통해 원하는 응답을 주려고 했다.
하지만 스프링 시큐리티
에서 제공하는 AccessDeniedException
을 발견하고 이 예외를 사용했다.
AccessDeniedException
이 던져졌을 때 받을 수 있는 handler
를 구현해야 한다.
AccessDeniedHandler
인터페이스를 implement
해서 구현체를 생성한다. 그리고 handle
메서드를 Override
해서 원하는 응답을 정의한다.
예시
@Component
@Slf4j
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.error("AccessDeniedException", accessDeniedException);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "권한이 없습니다.");
}
}
이 구현체를 Spring Security Config
에 설정해야 한다.
예시
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final AccessDeniedHandlerImpl accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
...
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
...
}
}
403 응답
{
"timestamp": 시간,
"status": 403,
"error": "Forbidden",
"path": "/test" // 에러 발생한 경로
}