Spring Security 메모 - Filter

timothy jeong·2022년 7월 17일
0

Spring Security

목록 보기
7/8

Security & Filter

기존에 제공되는 인가, 인증은 한계가 있다.

아래와 같은 것들을 할 수 없다.

  • Input validation
  • Tracing, Auditing and reporting
  • Logging of input like IP Address etc.
  • Encryption and Decryption
  • Multi factor authentication using OTP

그렇기 때문에 filter 를 직접 조작하고자하는 니즈가 있다.

지금까지 우리는 Authentication filter, Authorization filter, CSRF filter, CORS filter 를 어떻게 이용하는지 확인했던 것이다.
그리고 filter 는 filter chain 을 가질 수 있다.

사실 스프링시큐리티는 이러한 filter 들을 조합해서 만들어졌다.

// SpringBootApplication 계층에 이걸 추가하고
@EnableWebSecurity(debug = true)// application.properties 에 이걸 추가하면 security 가 작동시키는 filter 를 확인할 수 있다.	logging.level.org.springframework.security.web.FilterChainProxy=DEBUG 

정확히는 아래의 filter 들이 있고, 이들을 조작함으로써 우리는 spring security 의 행위를 정의한다. (물론 이것보다 더 많은 필터들이 있다.)
[image:B8244BEF-0294-4A59-B161-27C00BFECAC8-37344-00001558ED2A1B3C/page42image7643264.png]

Custom Filter 만들기

Filter 인터페이스를 구현함으로써 만들 수 있다.

// 아래의 함수를 구현하여 원하는 위치에 filter 를 넣을 수 있다.
addFilterBefore(filter, class)
addFilterAfter(filter, class)
addFilterAt(filter, class)

Validation 을 하는 filter 를 cors -> cerf -> { 여기 } -> Authentication 에 넣어보자.

[image:FE810CCC-3D70-47A0-82D7-1324226B0D98-37344-0000155D32F69C07/스크린샷 2022-07-14 오후 7.58.36.png]

public class RequestValidationBeforeFilter implements Filter {

	public static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
	private Charset credentialsCharset = StandardCharsets.UTF_8;

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
// 서블렛 말고 HttpServletRequest 를 원하므로 바꾼다.
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;
		String header = req.getHeader(AUTHORIZATION);
		if (header != null) {
			header = header.trim();
			if (StringUtils.startsWithIgnoreCase(header, AUTHENTICATION_SCHEME_BASIC)) {
				byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
				byte[] decoded;
				try {
					decoded = Base64.getDecoder().decode(base64Token);
					String token = new String(decoded, getCredentialsCharset(req));
					int delim = token.indexOf(":");
					if (delim == -1) {
						throw new BadCredentialsException("Invalid basic authentication token");
					}
					String email = token.substring(0, delim);
					if(email.toLowerCase().contains("test")) {
// HttpServletResponse.SC_BAD_REQUEST 를 해서 바로 reject 한다.
						res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
						return;
					}
				} catch (IllegalArgumentException e) {
					throw new BadCredentialsException("Failed to decode basic authentication token");
				}
			}
		}
		chain.doFilter(request, response);
	}

	protected Charset getCredentialsCharset(HttpServletRequest request) {
		return getCredentialsCharset();
	}

	public Charset getCredentialsCharset() {
		return this.credentialsCharset;
	}

}

이런식으로 필터를 짰으면, config 에서 필터를 더해줘야한다.

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http.cors().configurationSource(new CorsConfigurationSource() {
			@Override
			public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
				CorsConfiguration config = new CorsConfiguration();
				config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
				config.setAllowedMethods(Collections.singletonList("*"));
				config.setAllowCredentials(true);
				config.setAllowedHeaders(Collections.singletonList("*"));
				config.setMaxAge(3600L);
				return config;
			}
		}).and().csrf().ignoringAntMatchers("/contact").csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// 여기서 필터를 더해준다.
				.and().addFilterBefore(new RequestValidationBeforeFilter(), BasicAuthenticationFilter.class)
				.addFilterAfter(new AuthoritiesLoggingAfterFilter(), BasicAuthenticationFilter.class)
				.addFilterAt(new AuthoritiesLoggingAtFilter(), BasicAuthenticationFilter.class)
				.authorizeRequests()
				.antMatchers("/myAccount").hasRole("USER")
				.antMatchers("/myBalance").hasAnyRole("USER","ADMIN")
				.antMatchers("/myLoans").hasRole("ROOT")
				.antMatchers("/myCards").hasAnyRole("USER","ADMIN")
				.antMatchers("/user").authenticated()
				.antMatchers("/notices").permitAll()
				.antMatchers("/contact").permitAll().and().httpBasic();
	}

}

@Bean
public PasswordEncoder passwordEncoder() {
	return new BCryptPasswordEncoder();
}

addFilterAt(…, …) ??

addBefore, addAfter 는 알겠는데 At 은 뭐냐
이 경우에는 둘 중 어떤 필터가 더 먼저 실행될지 장담할 수 없다. 그러므로 어떤 이슈가 발생할지 알 수 없다.

GenericFilterBean & OncePerRequestFilter

이런거 다 필요없이 그냥 Filter 를 구현해도 된다.

GenericFilterBean: 일반적으로 필터를 작성하는데 필요한 모든 요소가 갖춰져 있다.
더 디테일하게 filter 를 제어할 수 있다.

OncePerRequestFilter: 각 요청마다 단 한번만 invoke 되도록 하는 필터이다.
내가 원하는 시나리오에서 필터가 동작하지 않도록 할 수 있다.

profile
개발자

0개의 댓글