3.2. 필터

Acegi Security는 여러 가지 필터를 사용하는데, 필터에 관해서는 본 참조 가이드의 나머지 부분에 걸쳐 설명할 것이다. 여러분은 이러한 필터들이 웹 애플리케이션에 추가되는 방법을 선택할 여지가 있는데, 여러분은 FilterToBeanProxyFilterChainProxy를 사용할 수 있다. 두 가지 모두 아래에서 알아볼 것이다.

대부분의 필터들은 FilterToBeanProxy를 이용하여 설정한다. 다음은 web.xml의 설정 예이다:

<filter>
  <filter-name>Acegi HTTP Request Security Filter</filter-name>
  <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
  <init-param>
    <param-name>targetClass</param-name>
    <param-value>org.acegisecurity.ClassThatImplementsFilter</param-value>
  </init-param>
</filter>

web.xml에 들어 있는 필터는 실제로는 FilterToBeanProxy이며 실질적으로 필터의 로직을 구현할 필터는 아니다. FilterToBeanProxy가 하는 일은 Spring 애플리케이션 컨텍스트로부터 획득한 빈에 필터의 메소드를 위임하는 것이다. 이렇게 하면 빈이 Spring 애플리케이션 컨텍스트 라이프 사이클 지원과 설정의 유연함으로부터 오는 이점을 얻을 수 있다. 빈은 javax.servlet.Filter를 구현해야 한다.

FilterToBeanProxy는 오직 하나의 초기화 매개변수만을 필요로 하는데, 초기화 매개변수는 targetClasstargetBean이다. targetBean이 빈의 이름으로 객체를 정하는 반면, targetClass 매개변수는 애플리케이션 컨텍스트의 첫 번째 객체를 지정된 클래스에 위치시킨다. 보통의 Spring 웹 애플리케이션들과 같이 FilterToBeanProxyWebApplicationContextUtils.getWebApplicationContext(ServletContext)를 통해 애플리케이션 컨텍스트에 접근하므로 여러분은 web.xml에 ContextLoaderListener를 설정해야만 한다.

서블릿 컨테이너 대신 IoC 컨테이너상에서 Filter를 작동하게 하는 경우 고려해야할 라이프 사이클에 관련된 문제가 하나 있다. 구체적으로 말하자면 어느 컨테이너가 Filter의 "구동" 및"종료" 메소드를 호출할 책임이 있느냐이다. 필터의 초기화 및 소멸 순서는 서블릿 컨테이너마다 매우 다양할 수 있는데, 따라서 이렇게 될 경우 한 Filter가 앞서 초기화된 Filter에 의해 수립된 구성 설정에 의존하는 경우 문제가 발생할 수 있는 것으로 알려져 있다. 반면 Spring IoC 컨테이너는 잘 알려진 인터페이스 계약, 예상 가능한 메소드 호출 순서, 빈 자동묶기(autowiring) 지원, 그리고 Spring 인터페이스 구현을 생략할 수 있는 선택권(예, Spring XML의 destroy-method 메소드) 뿐만 아니라 좀 더 포괄적인 라이프 사이클/IoC 인터페이스(InitializingBean, DisposableBean, BeanNameAware, ApplicationContextAware 등과 같은)를 가진다. 이러한 연유로 가능하면 서블릿 컨테이너 라이프 사이클 서비스 보다는 Spring의 라이프 사이클 서비스를 사용할 것을 권장한다. 기본적으로 FilterToBeanProxy는 프록시화된 빈에 init(FilterConfig)destroy() 메소드를 위임하지 않을 것이다. 여러분이 이러한 메소드 호출이 위임되기를 원한다면 lifecycle 초기화 매개변수를 servlet-container-managed로 설정한다.

FilterToBeanProxy를 사용하기 보다는 FilterChainProxy를 사용하길 강력히 권장한다. FilterToBeanProxy가 매우 유용한 클래스이긴 하지만 문제는 몇 가지 필터를 더 사용하게 될 경우 web.xml 파일 안의 <filter><filter-mapping>에 입력해야할 코드 라인이 폭발적으로 증가한다는 점이다. 이러한 문제를 극복하기 위해 Acegi Security는 FilterChainProxy 클래스를 제공하고 있다. FilterChainProxyFilterToBeanProxy를 이용하여 묶이기는 하나(바로 위 예제에서 처럼), 대상 클래스가 org.acegisecurity.util.FilterChainProxy이다. 필터 체인은 아래와 같은 코드를 이용하여 애플리케이션 컨텍스트내에 선언한다:

<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
  <property name="filterInvocationDefinitionSource">
    <value>
      CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
      PATTERN_TYPE_APACHE_ANT
      /webServices/**=httpSessionContextIntegrationFilterWithASCFalse,basicProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor
      /**=httpSessionContextIntegrationFilterWithASCTrue,authenticationProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor
    </value>
  </property>
</bean>        

여러분이 FilterSecurityInterceptor를 선언하는 방식과 비슷하다는 것을 알아챌 지도 모르겠다. 둘 모두 정규 표현식과 Ant 스타일의 Path를 지원하며 가장 구체적인 URI가 먼저 나타난다. 런타임시에는 FilterChainProxy가 현재 요청되는 웹 요청과 일치하는 첫 번째 URL 패턴을 정할 것이다. 각 해당 설정 속성들은 애플리케이션 컨텍스트내에 정의되어 있는 빈의 이름을 나타낸다. 그런 다음 필터들은 정해진 FilterChain 행위에 따라 지정된 순서대로 호출될 것이다(Filter가 처리를 끝내고자 할 경우에는 체인에 대한 처리를 진행하지 않도록 결정할 수도 있다).

여러분도 알 수 있겠지만 FilterChainProxy는 서로 다른 요청 패턴에 대한 중복된 필터 이름을 필요로 한다(위 예제에서는 exceptionTranslationFilterfilterSecurityInterceptor가 중복되어 있다). 설계상 이렇게 하도록 결정함으로써 FilterChainProxy는 서로 다른 URI 패턴에 대해 서로 다른 Filter 호출 순서를 지정할 수 있고 표현력을 향상시킬 수 있으며(정규 표현식, Ant 스타일의 Path, 커스텀 FilterInvocationDefinitionSource 구현 관점에서), 그리고 어떠한 Filter들이 호출되어야 하는지를 명확하게 할 수 있다.

여러분이 필터 체인 안에 두 개의 HttpSessionContextIntegrationFilter가 선언되어 있다는 것을 알고 있을지도 모르겠다(ASCallowSessionCreation의 줄임말이며, HttpSessionContextIntegrationFilter의 프로퍼티이다). 웹 서비스는 차후에 이루어지는 요청에는 jsessionid를 제시하지 않을 것이므로 사용자 에이전트에 대한 HttpSession를 생성하는 것은 낭비일 수 있다. 만약 여러분이 최대의 확장성(scalability)를 요하는 대형(high-volume) 애플리케이션을 개발하고 있다면 위에 나타나 있는 접근법을 취할 것을 권장한다. 규모가 더 작은 애플리케이션에는 단일 HttpSessionContextIntegrationFilter 만으로도(기본 allowSessionCreationtrue로 지정) 충분할 것이다.

라이프 사이클 문제와 관련하여, FilterChainProxy에 대해 init(FilterConfig)destroy() 메소드가 호출되었다면, FilterChainProxy는 항상 그러한 메소드들을 기저의 Filter에 위임할 것이다. 이 경우 FilterChainProxyFilterInvocationDefinitionSource에 의해 몇 번이나 선언되었는지와는 관계없이 각 Filter에 대한 초기화와 종료가 한 번만 이루어 지도록 보장해 준다. 여러분은 FilterChainProxy를 프록시화하는 FilterToBeanProxy에 들어있는 lifecycle 초기화 매개변수를 통해 이러한 메소드들의 호출 여부에 대한 총괄적인 선택권을 제어한다. 위에서 논의한 바와 같이 기본적으로는 어떠한 서블릿 컨테이너 라이프사이클 호출도 FilterChainProxy로 위임되지는 않는다.

또한 여러분은 <URI Pattern> = <Filter Chain> 표현식 우측편에 #NONE# 토큰을 사용하여 필터 체인에서 URI 패턴을 생략할 수도 있다. 예를 들어 위 예제의 경우에는 여러분이 /webservices 위치를 완전히 생략하고자 한다면, 여러분은 빈 설정에서 해당 라인을 아래와 같이 변경할 수 있다:

/webServices/**=#NONE#
        

한 가지 알아둘 것은 이 패턴과 일치하는 것은 모두 아무런 인증이나 권한부여 서비스가 적용되지 않으므로 누구나 이곳에 자유로이 접근할 수 있을 것이라는 점이다.

web.xml에 필터가 정의되는 순서는 매우 중요하다. 실제로 여러분이 어떠한 필터를 사용하고 있는지와는 관계없이 <filter-mapping>의 순서는 아래와 같아야 한다:

  1. ChannelProcessingFilter : 다른 프로토콜로 재지정(redirect)해야할 수도 있기 때문임

  2. ConcurrentSessionFilter : SecurityContextHolder의 기능을 사용하지는 않지만, SessionRegistry를 갱신하여 현재 인증주체로부터 진행되고 있는 요청을 나타낼 필요가 있기 때문임

  3. HttpSessionContextIntegrationFilter : SecurityContext가 웹 요청 시작시 SecurityContextHolder내에 설정될 수 있으며, 웹 요청이 종료되는 경우(다음 웹 요청 사용을 준비) SecurityContext에 대한 모든 변경사항들이 HttpSession에 복사될 수 있음

  4. 인증 처리 메커니즘(Authentication processing mechanisms) : AuthenticationProcessingFilter, CasProcessingFilter, BasicProcessingFilter, HttpRequestIntegrationFilter, JbossIntegrationFilter 등을 지정하여 SecurityContextHolder에 유효한 Authentication 요청 토큰이 포함되도록 변경할 수 있음.

  5. SecurityContextHolderAwareRequestFilter : 여러분이 SecurityContextHolderAwareRequestFilter을 이용하여 서블릿 컨테이너에 Acegi Security를 인식하는 HttpServletRequestWrapper을 설치하는 경우

  6. RememberMeProcessingFilter : 이전에 SecurityContextHolder을 갱신하는 인증 처리 메커니즘이 없었으며, 요청이 remember-me 서비스를 발생시키는 쿠키를 제시하여 적절히 기억된 Authentication 객체가 놓여지는 경우

  7. AnonymousProcessingFilter : 이전에 SecurityContextHolder를 갱신하는 인증 처리 메커니즘이 없었으며, 익명 Authentication 객체가 놓여지는 경우

  8. ExceptionTranslationFilter : Acegi Security 예외를 잡아내어 HTTP 오류 응답이 반환되거나 적절한 AuthenticationEntryPoint가 실행될 수 있도록 하기 위함

  9. FilterSecurityInterceptor : 웹 URI를 보호하기 위함

위의 필터들은 모두 FilterToBeanProxy이거나 FilterChainProxy이다. 각 애플리케이션에 대한 하나의 FilterChainProxy를 통해 하나의 FilterToBeanProxy만을 프록시화할 것을 권장하며, 그러한 FilterChainProxy가 모든 Acegi Security Filter들을 정의하도록 한다.

만약 여러분이 SiteMesh를 사용하고 있다면 SiteMesh 필터가 호출되기 전에 Acegi Security 필터가 실행되도록 한다. 이렇게 하면 SiteMesh 데코레이터에 의해 사용되는 적절한 시점에 SecurityContextHolder에 정보들이 설정되도록 할 수 있다.