IT

스프링 시큐리티 인 액션 9장

프로개발러 2023. 8. 12. 15:00
반응형

소스는 아래에서 다운 가능합니다.

https://github.com/wikibook/spring-security

 

GitHub - wikibook/spring-security: 《스프링 시큐리티 인 액션》 예제 코드

《스프링 시큐리티 인 액션》 예제 코드. Contribute to wikibook/spring-security development by creating an account on GitHub.

github.com

 

컨트롤러 소스

@RestController
public class HelloController {

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

 

configuration 파일

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new RequestValidationFilter(),BasicAuthenticationFilter.class)
            .addFilterAfter(new AuthenticationLoggingFilter(),BasicAuthenticationFilter.class)
            .authorizeRequests()
                .anyRequest()
                    .permitAll();
    }
}

 

addFilterBefore는 정된 필터 앞에 커스텀 필터를 추가

addFilterAfter는 지정된 필터 뒤에 커스텀 필터를 추가

 

addFilterAt은 지정된 필터의 순서에 커스텀 필터가 추가된다

 

그래서 curl로 최초 컨트롤러 진입시 제일 먼저 디버깅 걸리는 부분이

 

http.addFilterBefore(new RequestValidationFilter(),BasicAuthenticationFilter.class)

 

requestvalidationfilter부분

public class RequestValidationFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        var httpRequest = (HttpServletRequest) request;
        var httpResponse = (HttpServletResponse) response;
        String requestId = httpRequest.getHeader("Request-Id");
        if (requestId == null || requestId.isBlank()) {
            httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        filterChain.doFilter(request, response);
    }
}

BasicAuthenticationFilter보다 requestvalidationfilter가 먼저걸림.

public class RequestValidationFilter implements Filter {

필터를 재정의하기 위해서는 인터페이스인 filter를 implements해주고

dofilter메소드를 오버라이딩 해야 함.

 

header에 아무것도 넘기지 않았으므로 null이 찍힘.

 

실제로 400에러 뜸

 

 

curl 에서 확인하기 위해서는 -v 옵션 사용

 

  Trying ::1:8080...

* Connected to localhost (::1) port 8080 (#0)

> GET /hello HTTP/1.1

> Host: localhost:8080

> User-Agent: curl/7.77.0

> Accept: */*

> 

* Mark bundle as not supporting multiuse

< HTTP/1.1 400 

< X-Content-Type-Options: nosniff

< X-XSS-Protection: 1; mode=block

< Cache-Control: no-cache, no-store, max-age=0, must-revalidate

< Pragma: no-cache

< Expires: 0

< X-Frame-Options: DENY

< Content-Length: 0

< Date: Sat, 12 Aug 2023 05:41:03 GMT

< Connection: close

< 

* Closing connection 0

 

 

헤더값에 값을 넘겨주기 위해서는 아래와 같이 하면 됨.

curl -v -H "Request-Id:12345" localhost:8080/hello

 

그러면 실제로 아래 디버깅에서 찍어보면 값 나오는 것을 확인.

 

 

그리고 나서 authenticationfilter에서

filterafter를 통해서 값이 로그에 찍히는 것을 확인할 수 있음.

.addFilterAfter(new AuthenticationLoggingFilter(),BasicAuthenticationFilter.class)

public class AuthenticationLoggingFilter implements Filter {

    private final Logger logger =
            Logger.getLogger(AuthenticationLoggingFilter.class.getName());

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        var httpRequest = (HttpServletRequest) request;
        String requestId = httpRequest.getHeader("Request-Id");
        logger.info("Successfully authenticated request with id " +  requestId);
        filterChain.doFilter(request, response);
    }
}

 

 

 INFO 22710 --- [nio-8080-exec-9] c.l.s.f.AuthenticationLoggingFilter      : Successfully authenticated request with id 12345

 

 

 

두번째 프로젝트에서는

configuration파일에 addfilterat 을 사용해서 특정 시점에 필터링을 함.

autowired를 통해서 static필터 클래스 의존성 주입


@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private StaticKeyAuthenticationFilter filter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAt(filter,
                BasicAuthenticationFilter.class)
                .authorizeRequests()
                .anyRequest()
                    .permitAll();
    }

}

 

statickeyauthenticationfilter는  헤더에 있는 authorization값을 가져와서 값이 일치하는지 판단.


@Component
public class StaticKeyAuthenticationFilter implements Filter {

    @Value("${authorization.key}")
    private String authorizationKey;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        var httpRequest = (HttpServletRequest) request;
        var httpResponse = (HttpServletResponse) response;

        String authentication = httpRequest.getHeader("Authorization");

        if (authorizationKey.equals(authentication)) {
            filterChain.doFilter(request, response);
        } else {
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}

 

그리고 주의할 점은

같은 위치에 필터를 여러개 추가할 수 있지만

이럴경우 순서가 보장되지 않는다고 하니 주의

 

올바르게 값이 검증되면 아래와 같이 결과값 보임

  Trying ::1:8080...

* Connected to localhost (::1) port 8080 (#0)

> GET /hello HTTP/1.1

> Host: localhost:8080

> User-Agent: curl/7.77.0

> Accept: */*

> Authorization:SD9cICjl1e

> 

* Mark bundle as not supporting multiuse

< HTTP/1.1 200 

< X-Content-Type-Options: nosniff

< X-XSS-Protection: 1; mode=block

< Cache-Control: no-cache, no-store, max-age=0, must-revalidate

< Pragma: no-cache

< Expires: 0

< X-Frame-Options: DENY

< Content-Type: text/plain;charset=UTF-8

< Content-Length: 6

< Date: Sat, 12 Aug 2023 05:55:23 GMT

 

 

그리고 뒤에 필터 확장방법에 대해 나오는데

 

GenericFilterBean은 기존 Filter에서 얻어올 수 없는 정보였던 Spring의 설정 정보를 가져올 수 있게 확장된 추상 클래스이다.

 

 

이 필터는 한 가지 공통점이 있는데 매 서블릿 마다 호출이 된다는 것이다.

서블릿은 사용자의 요청을 받으면 서블릿을 생성해 메모리에 저장해두고, 같은 클라이언트의 요청을 받으면 생성해둔 서블릿 객체를 재활용하여 요청을 처리한다.

여기까지는 서블릿의 기본적인 내용이다.

문제는 이 서블릿이 다른 서블릿으로 dispatch되는 경우가 있을 수 있다.

가장 대표적으로 Spring Security에서 인증과 접근 제어 기능이 Filter로 구현되어진다.

이러한 인증과 접근 제어는 RequestDispatcher 클래스에 의해 다른 서블릿으로 dispatch되게 되는데, 이 때 이동할 서블릿에 도착하기 전에 다시 한번 filter chain을 거치게 된다.

바로 이 때 또 다른 서블릿이 우리가 정의해둔 필터가 Filter나 GenericFilterBean로 구현된 filter를 또 타면서 필터가 두 번 실행되는 현상이 발생할 수 있다.

이런 문제를 해결하기 위해 등장한 것이 바로 OncePerRequestFilter이다.

OncePerRequestFilter는 그 이름에서도 알 수 있듯이 모든 서블릿에 일관된 요청을 처리하기 위해 만들어진 필터이다.

이 추상 클래스를 구현한 필터는 사용자의 한번에 요청 당 딱 한번만 실행되는 필터를 만들 수 있다. 

 

참고

https://minkukjo.github.io/framework/2020/12/18/Spring-142/

반응형