ABOUT ME

포소니는 나의 하루이자 누군가의 공감입니다. 조용히 머물러도 좋고, 가볍게 이야기를 남겨도 좋아요. 당신의 일상도, 이곳에 스며들길 바랍니다.

Today
Yesterday
Total
  • Spring Security with CustomFilter
    Tech/SpringBoot 2018. 8. 5. 12:46
    반응형


    /**
    * 기존 프로젝트에서는 클라이언트와 서버간 인증 흐름은 대체로 아래와 같다.
    * spring security 에서 제공하는 PasswordEncoder 사용
    * - client 가 password 를 Sha256 으로 인코딩 하여 서버로 전달
    * - server 는 인코딩 된 패스워드를 passwordEncoder 를 사용하여 DB 에 저장된 패스워드와 match 검사를 한다
    * passwordEncoder 를 사용하면 내부적으로 match 검사를 한다.
    * -> Basic 인증을 사용한다고 가정.
    * 변경후:
    * client 와 server 에 보안 인증 모듈을 추가로 탑재 하게 되었다.
    * 공개키 방식의 보안 인증 과정은 아래와 같다.
    * - client 는 서버가 공개한 public key 로 암호화 하여 Server 로 전달
    * - Server는 public key로 암호화된 client 의 data를 private key 로 복호화
    * 해당 방식을 그냥 도입할수는 있겠지만 기존 사용자들에 대해서 대응 하기 위해 아래와 같이 해결 하였다.
    *
    * HttpSecurity를 수행하기전 필터 처리를하도록 addFilterBefore 를 통해 CustormFilter를 등록 하였다.
    * CustomFilter 에서는 Request 의 header 값에서 public key 로 암호화된 데이터를 복호화 한 후 다시 기존 방식인 sha256 으로 인코딩 하여
    * 인증 로직으로 전달 하도록 하였다.
    */

    /**
    * Spring Request 흐름을 잠깐 정리 한다면?
    * - client 의 Request 요청은 최초에 Filter 로 전달 되게 된다.
    - 또한 Filter 는 chain 으로 전달 할 수 있다.
    - Filter 로 전달된 데이터는 다시 DispatcherServlet 으로 전달 된다.
    - 이 후 인증 / 뷰 / 인터셉터 등을 거쳐 end-point 인 controller 로 전달 된다.
    * Behind:
    * 초기에는 spring Intercepter 를 사용 하려고 하였으나, Request 를 modify 할 수 없기 때문에 포기 함.
    */

    //Spring 은 Filter 를 다양하게 제공 하고 있다.

    // Spring 의 OncePerFilter 를 상속 하게 되면 DoFilterInternal 하나만 오버라이드 하게 된다.
    // OncePerRequestFilter 는 GenericFilterBean 을 extends 하여 구현됨.
    public class AuthorizationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
    HttpServletResponse httpServletResponse,
    FilterChain filterChain) throws ServletException, IOException { }
    }

    //Spring 의 GenericFilterBean 을 상속하게 되면 doFilter 하나만 오버라이드 하게 된다.
    //GenericFilterBean 은 Filter 를 extends 하여 구현됨.
    public class AuthorizationFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest servletRequest,
    ServletResponse servletResponse,
    FilterChain filterChain) throws IOException, ServletException {
    }
    }
    //Servlet 의 Filter 를 extends 하게 되면 아래 처럼 강제적으로 오버라이드 해주어야 한다.
    public class AuthorizationFilter extends Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void doFilter(ServletRequest servletRequest,
    ServletResponse servletResponse,
    FilterChain filterChain) throws IOException, ServletException {}
    @Override
    public void destroy() {}
    }

    // 적절하게 찾아서 커스터마이징 하면 될것 같다.
    // 우선 프로젝트에서는 OncePerRequestFilter 를 사용하도록 하였다.
    // Request 데이터를 가져온 후 복호화 하고, Request 데이터를 다시 setup 하였다.
    // Request 데이터를 재설정 하는 방법은 아래 링크를 참조 함.
    //https://stackoverflow.com/posts/23590606/revisions

    public class CustomFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
    HttpServletResponse httpServletResponse,
    FilterChain filterChain) throws ServletException, IOException {
    // Request header 값을 변경
    // HttpServletRequestWrapper 를 사용하여 Original Request 데이터를 변경후 필터 체인으로 전달
    filterChain.doFilter(changeRequest,response);
    }
    }

    // 해당 필터를 아래와 같이 등록 한다.
    public class WebSecurityContext extends WebSecurityConfigurerAdapter {
    .
    .
    .
    // 인증 실패 및 예외 발생에 대한 엔트리 포인트
    @Bean(name = "customAuthEntryPoint")
    public CustomAuthEntryPoint customAuthEntryPoint() {
    return new CustomAuthEntryPoint();
    }
    // 액세스 거부 핸들러 등록
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
    return new CustomAccessDeniedHandler();
    }
    @Bean
    public CustomFilter customFilter() {
    return new CustomFilter();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    .
    .
    .
    //bassic 인증을 사용할 경우 아래 와 같이 filter 등록시 해당 필터를 등록 해준다.
    http.addFilterBefore(customFilter(),BasicAuthenticationFilter.class);
    http.httpBasic().authenticationEntryPoint(authenticationEntryPoint());
    http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
    }
    }


    반응형
Designed by Tistory.