Servlet&JSP 시리즈의 9.필터(Filter) 와 리스너(Listener) 개념 및 실습을 참고하자
Filter Web Application
에서 관리되는 영역Spring Boot Framework
에서 Client로 부터 오는 요청/응답에 대해서ServletRequest
, ServletResponse
의 객체 변환 가능Spring Framework
에서는request
/ response
의 Logging 용도로 활용하거나,Service business logic
과 분리 시킨다.인텔리제이를 사용한다
이번 실습에서는 Lombok 사용
build.gradle
파일에 가보면 잘 추가되어있음
컴파일할때 작동
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
lombok 사용한 User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
}
lombok을 이용한 객체 사용시 @Slf4j
를 사용
시스템 아웃말고 log 사용가능
@Slf4j
@RestController
@RequestMapping("/api/user")
public class ApiController {
@PostMapping("")
public User user(@RequestBody User user) {
log.info("User : {}", user);
return user;
}
}
@Slf4j
@Component
public class GlobalFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 전처리
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String url = httpServletRequest.getRequestURI();
BufferedReader br = httpServletRequest.getReader();
br.lines().forEach(line -> {
log.info("url : {}, Line : {}", url, line);
});
chain.doFilter(request, response);
// 후처리
// 다음에
}
}
{
"name" : "yeppi",
"age" : 11
}
BufferedReader
에서 이미 데이터를 끝까지 읽어버린 상태이다.// 전처리
ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper((HttpServletResponse) response);
doFilter()
가 실행된 후에 작성해야 함(위 예제 참고)@Slf4j
@Component
public class GlobalFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 전처리
ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper((HttpServletResponse) response);
String url = httpServletRequest.getRequestURI();
chain.doFilter(httpServletRequest, httpServletResponse);
// 후처리
String reqContent = new String(httpServletRequest.getContentAsByteArray());
log.info("response url : {}, responseBody : {}", url, reqContent);
String resContent = new String(httpServletResponse.getContentAsByteArray());
int httpStatus = httpServletResponse.getStatus();
log.info("response status : {}, responseBody : {}", httpStatus, resContent);
}
}
{
"name" : "yeppi",
"age" : 11
}
httpServletResponse.copyBodyToResponse();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 전처리
. . .
// 후처리
. . .
String resContent = new String(httpServletResponse.getContentAsByteArray());
int httpStatus = httpServletResponse.getStatus();
httpServletResponse.copyBodyToResponse(); // 복사
. . .
}
ApiController.java
를 복사해서 temp 로 변경
@Slf4j
@RestController
@RequestMapping("/api/temp")
public class ApiUserController {
@PostMapping("")
public User user(@RequestBody User user) {
log.info("temp : {}", user);
return user;
}
}
FilterApplication
.java@ServletComponentScan
을 추가GlobalFilter
.java@Component
대신 @WebFilter
사용
user 하위의 모든 주소 매칭 ⇒ temp 는 매칭되지 않음
@Slf4j
@WebFilter(urlPatterns = "/api/user/**")
public class GlobalFilter implements Filter { . . . }
{
"name" : "yeppi",
"age" : 11
}
ApiController
.java)가 잘 출력 Filter와 매우 유사한 형태로 존재
Filter와 차이점은 Spring Context에 등록되다는 것
AOP와 유사한 기능 제공
인증 단계를 처리 or Logging 처리
👉 이를 선/후 처리 함으로써, Service business logic과 분리 시킨다
인텔리제이를 사용한다
@Annotation
사용📌@RequiredArgsConstructor
생성자에서 동작@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
log.info("request url : {}", url);
return true;
// return false; // 동작 안함
}
private boolean checkAnnotation(Object handler, Class clazz) {
// resource javascript, html 등
if(handler instanceof ResourceHttpRequestHandler) {
return true;
}
// annotation check
HandlerMethod handlerMethod = (HandlerMethod) handler;
if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)) {
// Auth annoation 이 있을 때
return true;
}
return false;
}
}
@Auth
는 특정 클래스나 특정 메서드를 검사하고 싶을 때 사용
hello()
에 사용할 수 있음
@RestController
@RequestMapping("/api/private")
@Auth
@Slf4j
public class PrivateController {
@GetMapping("/hello")
public String hello() {
log.info("private hello controller");
return "private hello";
}
}
@Configuration
@RequiredArgsConstructor
public class MvcConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor);
}
}
@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
// 세션이나 쿠기대신 아래에서 검사할 데이터 넣기
URI uri = UriComponentsBuilder.fromUriString(request.getRequestURI())
.query(request.getQueryString())
.build()
.toUri();
log.info("request url : {}", url);
boolean hasAnnotation = checkAnnotation((handler), Auth.class);
log.info("has annotation : {}", hasAnnotation); // private true, public false
// 나의 서버는 모두 public으로 동작을 하는데
// 단, Auth 권한을 가진 요청에 대해서는 세션, 쿠키 등을 검사
if(hasAnnotation) {
// 권한 체크
String query = uri.getQuery();
log.info("query = {}", query);
if(query.equals("name=yeppi")) { // 이름이 yeppi 일때만 통과
return true;
}
return false;
}
return true;
// return false; // 동작 안함
}
private boolean checkAnnotation(Object handler, Class clazz) {
. . .
}
}
.addPathPatterns
추가registry.addInterceptor(authInterceptor).addPathPatterns("/api/private/*");
@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
. . .
if(hasAnnotation) {
// 권한 체크
. . .
// 권한 없다면
throw new AuthException();
}
return true;
// return false; // 동작 안함
}
private boolean checkAnnotation(Object handler, Class clazz) {
. . .
}
}
GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthException.class)
public ResponseEntity authException() {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // UNAUTHORIZED 401 error
}
}
특정 권한은? 인터셉터에서 검사
- 필터는 불가능
- 스프링Context 에서 관리가 되고 있음으로
어떤 클래스/컨트롤러의 데이터 흐름을 어노테이션으로 관리할 수 있음
필터
- 웹 애플리케이션에서 관리
- handler 오브젝트는 없음
VS
인터셉터
- 스프링Context 에서 관리
- 어노테이션, 클래스 사용 가능
- handler 오브젝트 사용 가능