본문 바로가기
SpringBoot

Spring Security with CustomFilter

by ByteBridge 2018. 8. 5.
반응형


/**
* 기존 프로젝트에서는 클라이언트와 서버간 인증 흐름은 대체로 아래와 같다.
* 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());
}
}


반응형