스프링부트 다양한 기능 5. Spring Boot Filter와 Interceptor

min seung moon·2021년 6월 29일
3

Spring

목록 보기
36/50
post-custom-banner

1. Filter

  • Filter란 Web Application에서 관리되는 영역으로써 Spring Boot Framework에서 Client로 부터 오는 요청/응답에 대해서 최초/최종 단계의 위치에 존재하며, 이를 통해서 요청/응답의 정보를 변경하거나, Spring에 의해서 데이터가 변환되기 전의 순수한 Client의 요청/응답 값을 확인 할 수 있다
  • 유일하게 ServletRequest, ServletResponse의 객체를 변환할 수 있다
  • 주로 Spring Framework에서는 request/response의 logging 용도로 활용하거나, 인증과 관련된 Logic들을 해당 Filter에서 처리한다
  • 이를 선/후 처리 함으로써, Service business logic과 분리 시킨다

2. Protject Test

  • https://start.spring.io/
    • filter project, Spring web, Lombok
  • Package : controller, dto
  • Class : ApiController, User
  • LOMBOK
    • Java 라이브러리로 반복되는 getter, setter, toString 등의 메서드 작성 코드를 줄여주는 코드 다이어트 라이브러리
    • 보통 Model 클래스나 Entity 같은 도메인 클래스 등에는 수많은 멤버변수가 있고 이에 대응되는 getter와 setter 그리고 toString() 메서드 그리고 때에 따라서는 멤버변수에 따른 여러개의 생성자를 만들어주게 되는데, 거의 대부분 이클립스같은 IDE의 힘만으로 생성한다고 하지만 이 역시도 번거로운 작업이 될 수 있습니다. 뿐만 아니라 코드 자체가 반복되는 메서드로 인해 매우 복잡해지게 됩니다.
    • Lombok은 여러가지 어노테이션을 제공하고 이를 기반으로 코드를 컴파일과정에서 생성해 주는 방식으로 동작하는 라이브러리입니다. 즉 코딩 과정에서는 롬복과 관련된 어노테이션만 보이고 getter와 setter 메서드 등은 보이지 않지만 실제로 컴파일된 결과물(.class)에는 코드가 생성되어 있다는 뜻입니다.
    • 출처: https://dololak.tistory.com/783 [코끼리를 냉장고에 넣는 방법]
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
  • User.java
    • @Getter
      • getter method
    • @Setter
      • setter method
    • @Data
      • getter, setter, toString ... 등의 메소드를 갖는 어노테이션
    • @NoArgsConstructor
      • 기본 생성자
    • @AllArgsConstructor
      • 전 field 생성자
package com.example.filter.dto;

import lombok.*;

// lombok
//@Getter  // getter method
//@Setter  // setter method
@Data // getter, setter, toString ... 등의 메소드를 갖는 어노테이션
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 전체 생성자
public class User {
    private String name;
    private int age;
}
  • ApiController.java
    • @Slf4j
      • System.out.println();이 아닌 log를 남길 수 있게 도와주는 lombok annotation
package com.example.filter.controller;

import com.example.filter.dto.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j // System.out.println();이 아닌 log를 남길 수 있게 도와준다
@RestController
@RequestMapping("/api")
public class ApiController {

    @PostMapping("/user")
    public User user(@RequestBody User user) {
        log.info("User : {}", user);
        return user;
    }
}

01. 간단한 Filter 전처리

  • Package : filter
  • Class : GlobalFilter
  • GlobalFilter.java
package com.example.filter.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

@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(httpServletRequest, httpServletResponse);

        // 후처리
    }
}





  • getReader() has already been called for this request
    • 이미 리더를 통해서 현재 받은 데이터를 모두 출력을 했기 때문에 controller에서 user 데이터에 접근을 할려고 했으나 데이터가 없어서 error가 발생

02. getReader() has already been called for this request ERROR 해결

  • GlobalFilter.java
    • ContentCachingRequestWrapper & ContentCachingResponseWrapper
      • 우선 필터를 통해 HttpServletResponse, HttpServletRequest 클래스로 들어온 request와 response를 ContentCachingRequestWrapper와 ContentCachingResponseWrapper로 래핑해주어야 한다.
      • HttpServletRequest 그대로 request.getReader 함수를 호출하거나 안에 있는 데이터를 읽으려고 하면, 단 한번만 읽을 수 있도록 톰캣에서 만들어두었기 때문에 이걸 다시 읽을 수 있는 클래스로 래핑해주어야 하기 때문이다.
      • 생성 시에는 read를 하지 않고 길이만 초기화를 시켜준다
    • doFilter()
      • doFilter가 실행이 되면서 실내 내부 Spring 안으로 들어가서야 writeToCache 메소드가 실행이 되서 request의 내용이content에 담겨 있게 되면서 읽을 수 있게 된다
      • 그렇기에 데이터 사용은 doFilter 이후에 처리해 준다
package com.example.filter.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class GlobalFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 우선 필터를 통해 HttpServletResponse, HttpServletRequest 클래스로 들어온 request와 response를 ContentCachingRequestWrapper와 ContentCachingResponseWrapper로 래핑해주어야 한다.
        // HttpServletRequest 그대로 request.getReader 함수를 호출하거나 안에 있는 데이터를 읽으려고 하면, 단 한번만 읽을 수 있도록 톰캣에서 만들어두었기 때문에 이걸 다시 읽을 수 있는 클래스로 래핑해주어야 하기 때문이다.
        ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper((HttpServletResponse) response);
        // 생성 시에는 read를 하지 않고 길이만 초기화를 시켜준다

        // 전처리
        chain.doFilter(httpServletRequest, httpServletResponse);
        // doFilter가 실행이 되면서 실내 내부 Spring 안으로 들어가서야
        // writeToCache 메소드가 실행이 되서 request의 내용이
        // content에 담겨 있게 되면서 읽을 수 있게 된다
        // 그렇기에 log는 doFilter 이후에 처리해 준다

        // 후처리
        // req
        String url = httpServletRequest.getRequestURI();
        String reqContent = new String(httpServletRequest.getContentAsByteArray());

        log.info("request url : {}, request body : {}", url, reqContent);

        String resContent = new String(httpServletResponse.getContentAsByteArray());
        int httpStatus = httpServletResponse.getStatus();
        log.info("response status : {}, response body : {}", httpStatus, resContent);

    }
}


  • 이번에는 잘 전송이 되었으나 후 처리 후 데이터를 읽어버려서 Body가 비게 되었다
    • 그렇기에 한 번 더 복사를 해주어야 한다

03. httpServletResponse.copyBodyToResponse(); 추가

  • 내가 만약 filter에서 response, request 찍어야 된다면 ContentCachingRequestWrapper, ContentCachingResponseWrapper를 랩핑해서 사용
  • request 사용 시 데이터는 getContentAsByteArray로 카피를 해서 사용
  • response도 getContentAsByteArray로 카피해서 출력하되 copyBodyToResponse를 해줘야 클라이언트가 정상적으로 응답을 받을 수 있다
  • GlobalFilter.java
package com.example.filter.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class GlobalFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 우선 필터를 통해 HttpServletResponse, HttpServletRequest 클래스로 들어온 request와 response를 ContentCachingRequestWrapper와 ContentCachingResponseWrapper로 래핑해주어야 한다.
        // HttpServletRequest 그대로 request.getReader 함수를 호출하거나 안에 있는 데이터를 읽으려고 하면, 단 한번만 읽을 수 있도록 톰캣에서 만들어두었기 때문에 이걸 다시 읽을 수 있는 클래스로 래핑해주어야 하기 때문이다.
        ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper((HttpServletResponse) response);
        // 생성 시에는 read를 하지 않고 길이만 초기화를 시켜준다

        // 전처리
        chain.doFilter(httpServletRequest, httpServletResponse);
        // doFilter가 실행이 되면서 실내 내부 Spring 안으로 들어가서야
        // writeToCache 메소드가 실행이 되서 request의 내용이
        // content에 담겨 있게 되면서 읽을 수 있게 된다
        // 그렇기에 log는 doFilter 이후에 처리해 준다

        // 후처리
        // req
        String url = httpServletRequest.getRequestURI();
        String reqContent = new String(httpServletRequest.getContentAsByteArray());

        log.info("request url : {}, request body : {}", url, reqContent);

        String resContent = new String(httpServletResponse.getContentAsByteArray());
        int httpStatus = httpServletResponse.getStatus();

        httpServletResponse.copyBodyToResponse();

        log.info("response status : {}, response body : {}", httpStatus, resContent);

    }
}


04. 특정 controller에만 Filter 적용

  • Class : ApiTempController
  • Main FilterApplication.java
    • @ServletComponentScan
      • @WebFilter를 사용하기 위한 어노테이션
package com.example.filter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class FilterApplication {

	public static void main(String[] args) {
		SpringApplication.run(FilterApplication.class, args);
	}

}
  • GlobalFilter.java
    • @WebFilter(urlPatterns = "")
      • @WebFilter(urlPatterns = "")
      • @Component를 제거하여 Bean에서 해제
      • 필터를 등록하고 설정하는 어노테이션
package com.example.filter.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/api/user/*")
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);

        // 전처리
        chain.doFilter(httpServletRequest, httpServletResponse);

        // 후처리

        // req
        String url = httpServletRequest.getRequestURI();
        String reqContent = new String(httpServletRequest.getContentAsByteArray());

        log.info("request url : {}, request body : {}", url, reqContent);

        // res
        String resContent = new String(httpServletResponse.getContentAsByteArray());
        int httpStatus = httpServletResponse.getStatus();

        httpServletResponse.copyBodyToResponse();

        log.info("response status : {}, response body : {}", httpStatus, resContent);

    }
}
  • ApiController.java
    • RequestMapping에 /user추가 및 postMapping은 빈칸으로 변경
package com.example.filter.controller;

import com.example.filter.dto.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j // System.out.println();이 아닌 log를 남길 수 있게 도와준다
@RestController
@RequestMapping("/api/user")
public class ApiController {

    @PostMapping("")
    public User user(@RequestBody User user) {
        log.info("User : {}", user);
        return user;
    }
}
  • ApiTempController.java
package com.example.filter.controller;

import com.example.filter.dto.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j // System.out.println();이 아닌 log를 남길 수 있게 도와준다
@RestController
@RequestMapping("/api/temp")
public class ApiTempController {

    @PostMapping("")
    public User user(@RequestBody User user) {
        log.info("Temp : {}", user);
        return user;
    }
}

3. Interceptor

  • Interceptor란 Filter와 매우 유사한 형태로 존재 하지만, 차이점은 Spring Context에 등록
  • AOP와 유사한 기능을 제공할 수 있으며, 주로 인증 단계를 처리 하거나, Logging를 하는데 사용한다
  • 이를 선/후 처리 함으로써, Service business logic과 분리 시킨다

4. Project Test

  • interceptor project
  • web & lombok
  • Package : controller
  • Class : PublicController, PrivateController
  • PublicController.java
    • 모든 사용자가 사용 가능한 api
package com.example.interceptor.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 모든 사용자가 사용 가능한 api
@RestController
@RequestMapping("/api/public")
public class PublicController {

    @GetMapping("/hello")
    public String hello() {
        return "public hello";
    }
}
  • PrivateController.java
    • 특정 조건이 만족한 사용자만 사용가능한 api
package com.example.interceptor.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/private")
public class PrivateController {

    @GetMapping("/hello")
    public String hello() {
        return "private hello";
    }

}

01. Interceptor을 활용하여 특정 어노테이션 접근 방지

  • Package : annotation, interceptor, config
  • Class : Auth, AuthInterceptor, MvcConfig
  • annotation / Auth.annotation
package com.example.interceptor.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Auth {
}
  • interceptor / AuthInterceptor.java
    • checkAnnotation
      • 특정 조건이 만족하면 Pass를 하고 만약에 조건에 만족하지 못하면 Block을 해라
package com.example.interceptor.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@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 false;
    }

    private boolean checkAnnotation(Object handler, Class clazz) {
        // resource (Java script 파일이라던지 html 파일 등)
        if(handler instanceof ResourceHttpRequestHandler) {
            return true;
        }

        // annotation check
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)) {
            // Auth annotation이 있을 때는 true
            return true;
        }

        return false;
    }
}
  • config / MvcConfig.java
    • Interceptor 등록을 해주는 클래스 안하면 못씀
    • @Configuration
      • 어노테이션기반 환경구성을 돕는다. 이 어노테이션을 구현함으로써 클래스가 하나 이상의 @Bean 메소드를 제공하고 스프링 컨테이가 Bean정의를 생성하고 런타임시 그 Bean들이 요청들을 처리할 것을 선언하게 된다.
    • @RequiredArgsConstructor
      • final로 선언된 객체들을 생성자에서 받을 수 있게 해준다, Spring에서는 @Autowired로도 받을 수 있지만 순환참조가 일어날 수 있다 그래서 최근에는 순환참조를 피하기 위해서 @RequiredArgsConstructor를 이용해서 생성자에서 받는경우가 많다
    • addInterceptors에서 등록
package com.example.interceptor.config;

import com.example.interceptor.interceptor.AuthInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


// final로 선언된 객체들을 생성자에서 받을 수 있게 해준다, Spring에서는 @Autowired로도 받을 수 있지만 순환참조가 일어날 수 있다 그래서 최근에는 순환참조를 피하기 위해서 @RequiredArgsConstructor를 이용해서 생성자에서 받는경우가 많
@Configuration
@RequiredArgsConstructor
public class MvcConfig implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor);
    }
}
  • controller / PrivateController.java
    • @Auth 추가
package com.example.interceptor.controller;

import com.example.interceptor.annotation.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/api/private")
@Auth
public class PrivateController {

    @GetMapping("/hello")
    public String hello() {
        log.info("private hello controller");
        return "private hello";
    }

}


  • 현재 클라이언트 응답을 제대로 받지 못했는데 그 이유는 AuthInteceptor.java 내부의 preHandle 메소드의 return을 false로 했기에 true로 변경하면 된다
  • AuthInteceptor.java
package com.example.interceptor.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@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;
    }

    private boolean checkAnnotation(Object handler, Class clazz) {
        // resource (Java script 파일이라던지 html 파일 등)
        if(handler instanceof ResourceHttpRequestHandler) {
            return true;
        }

        // annotation check
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)) {
            // Auth annotation이 있을 때는 true
            return true;
        }

        return false;
    }
}

02. 권한 체크 추가

-1. 먼저 작성한 checkAnnotation() 메소드가 잘 동작하는지 확인

  • AuthInterceptor.java
package com.example.interceptor.interceptor;

import com.example.interceptor.annotation.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@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);

        boolean hasAnnotation = checkAnnotation(handler, Auth.class);
        log.info("has annotation : {}",hasAnnotation);

        return true;
    }

    private boolean checkAnnotation(Object handler, Class clazz) {
        // resource (Java script 파일이라던지 html 파일 등)
        if(handler instanceof ResourceHttpRequestHandler) {
            return true;
        }

        // annotation check
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)) {
            // Auth annotation이 있을 때는 true
            return true;
        }

        return false;
    }
}


-2. 권한 추가하기

  • 나의 서버는 모두 public으로 동작을 하는데, 단! @Auth 권한을 가진 요청에 대해서는 세션, 쿠키, Param에서 검증
  • AuthInterceptor.java
    • URI uri = UriComponentsBuilder.fromUriString(request.getRequestURI()).query(request.getQueryString()).build().toUri();
      • 필요한 데이터를 바인딩하여 uri 값으로 대입
    • boolean hasAnnotation = checkAnnotation(handler, Auth.class);
      • @Auth 어노테이션을 갖고 있다면 권한을 체크한 후 페이지를 보여주도록 한다
      • 이번에는 QueryParam으로 권한 체크 진행
package com.example.interceptor.interceptor;

import com.example.interceptor.annotation.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;

@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);

        // 나의 서버는 모두 public으로 동작을 하는데
        // 단! @Auth 권한을 가진 요청에 대해서는 세션, 쿠키, Param에서 검증
        if(hasAnnotation) {
            // 권한체크
            String query = uri.getQuery();

            log.info(query);
            if(query.equals("name=steve")) {
                return true;
            }
            return false;
            
        }

        return true;
    }

    private boolean checkAnnotation(Object handler, Class clazz) {
        // resource (Java script 파일이라던지 html 파일 등)
        if(handler instanceof ResourceHttpRequestHandler) {
            return true;
        }

        // annotation check
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)) {
            // Auth annotation이 있을 때는 true
            return true;
        }

        return false;
    }
}



-3. 특정 url에만 Interceptor 적용

  • config / MvcConfig.java
    • addInterceptors() 메소드 내부의 registryaddPathPatterns("url")을 추가해서 특정 url에만 반응하도록 한다
      • url은 addPathPatterns("url", "url", ..) 계속 패턴을 추가할 수 있다
    • .excludePathPatterns()은 add의 반대로 제외하고 싶은 url 입력
package com.example.interceptor.config;

import com.example.interceptor.interceptor.AuthInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


// final로 선언된 객체들을 생성자에서 받을 수 있게 해준다, Spring에서는 @Autowired로도 받을 수 있지만 순환참조가 일어날 수 있다 그래서 최근에는 순환참조를 피하기 위해서 @RequiredArgsConstructor를 이용해서 생성자에서 받는경우가 많
@Configuration
@RequiredArgsConstructor
public class MvcConfig implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor).addPathPatterns("/api/pirvate/*");
    }
}





-4. 예외 처리 추가

  • Package : exception, handler
  • Class : AuthException, GlobalExceptionHandler
  • 만약에 권한을 갖지 못할경우 에러 발생
  • exception / AuthException.java
package com.example.interceptor.exception;

public class AuthException extends RuntimeException{

}
  • handler / GlobalExceptionHandler.java
package com.example.interceptor.handler;

import com.example.interceptor.exception.AuthException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthException.class)
    public ResponseEntity authException() {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
}
  • interceptor / AuthInterceptor.java
package com.example.interceptor.interceptor;

import com.example.interceptor.annotation.Auth;
import com.example.interceptor.exception.AuthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;

@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);

        // 나의 서버는 모두 public으로 동작을 하는데
        // 단! @Auth 권한을 가진 요청에 대해서는 세션, 쿠키, Param에서 검증
        if(hasAnnotation) {
            // 권한체크
            String query = uri.getQuery();

            log.info(query);
            if(query.equals("name=steve")) {
                return true;
            }
            throw new AuthException();

        }

        return true;
    }

    private boolean checkAnnotation(Object handler, Class clazz) {
        // resource (Java script 파일이라던지 html 파일 등)
        if(handler instanceof ResourceHttpRequestHandler) {
            return true;
        }

        // annotation check
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)) {
            // Auth annotation이 있을 때는 true
            return true;
        }

        return false;
    }
}



profile
아직까지는 코린이!
post-custom-banner

0개의 댓글