Framework & Platform/Spring

spring security - How to have spring security context as child context

linuxism 2014. 1. 9. 22:47


I'm trying to have spring security context as a parent context, so I could have url security on the servlet context file.

I have:

  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath:/spring-security.xml
    </param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>myapp-soap</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
      <param-name>transformWsdlLocations</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>

on web.xml, general security configuration on spring-security.xml and

<!-- Authorization configurations -->
<security:http auto-config="false" use-expressions="true"
    create-session="never"
    authentication-manager-ref="authenticationManager"
    entry-point-ref="authenticationEntryPoint">

    <security:custom-filter
        position="PRE_AUTH_FILTER" ref="serviceAuthenticationFilter"/>

    <security:intercept-url
        pattern="/GetForbiddenUrl" access="hasRole('roleThatDoesntExist')" />
    <security:intercept-url pattern="/**" access="permitAll" />
</security:http>
<!-- annotation security -->
<security:global-method-security pre-post-annotations="enabled"/>

on the myapp-soap-servlet.xml. It doesn't work but fails with

ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/my-app/v1/soap]] (ServerService Thread Pool -- 192) JBWEB000284: Exception starting filter springSecurityFilterChain:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined

However, if I move <security:http> part to spring-security root context configuration, everything works. Shouldn't it work the way I try? How can I get url-based security in my child context?

I also tried combining the context files into one, but the same problem seems to occur.

share|improve this question
 
Are you sure that your spring-security.xml is picked up by spring? –  Maksym Demidas Jun 4 '13 at 15:05
 
@MaksymDemidas yes, as moving the http part results in all directives being used, in both parnt and child context –  eis Jun 4 '13 at 21:40
add comment

The DelegatingFilterProxy will by default look in the root ApplicationContext which means by default you need to place your <http> configuration there (it is what creates the springSecurityFilterChain).

However, you can specify use that DelegatingFilterProxy a different ApplicationContext by specifying the contextAttribute for it to use. To do this update your web.xml as shown below

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>contextAttribute</param-name>
        <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.myapp-soap</param-value>
    </init-param>
</filter>

A similar example using Spring Security 3.2+'s AbstractSecurityWebApplicationInitializer can be seen below:

public class SecurityApplicationInitializer extends
        AbstractSecurityWebApplicationInitializer {

    @Override
    protected String getDispatcherWebApplicationContextSuffix() {
        // NOTE: if you are using AbstractDispatcherServletInitializer or
        // AbstractAnnotationConfigDispatcherServletInitializer You probably
        // want this value to be "dispatcher"
        return "myapp-soap";
    }

}

This works because it modifies the name of the ServletContext attribute that DelegatingFilterProxyuses to lookup the ApplicationContext. Instead of using the default value which discovers the rootApplicationContext it now uses the attribute that your MessageDispatcherServlet is using (thus pointing to the child context).

Note that MessageDispatcherServlet's (or any subclass of FrameworkServlet such asDispatcherServlet) stores the ApplicationContext in the ServletContext using the attribute name "org.springframework.web.servlet.FrameworkServlet.CONTEXT." + <servlet-name>where <servlet-name> is the name of the servlet. So in this instance, the attribute that must be configured is org.springframework.web.servlet.FrameworkServlet.CONTEXT.myapp-soap. If you changed the servlet-name from myapp-soap to spring-servlet, then you would useorg.springframework.web.servlet.FrameworkServlet.CONTEXT.spring-servlet instead.

PS I think the subject should read "How to have spring security context as child context"

share|improve this answer


출처 - http://stackoverflow.com/questions/16920243/how-to-have-spring-security-context-as-parent-context







편의상 반말을 사용 하겠습니다.

보통 서블릿 필터를 등록 할 때에는 필터 내부에서는 스프링 빈을 주입 받지 못해 많이 불편하다.

그래서 스프링에서 필터를 지원 해주는 클래스가 존재 하는데 그 클래스가 DelegatingFilterProxy 라는 클래스 이다.

보통을 그냥 서블릿 필터를 등록해서 사용하면되나 문제는 Spring security 프레임워크를 사용 시에는 필수 사용 하게되게된다.

 <filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
<-- 이건 spring-security 설정시 필터 등록하는 부분 

이런 방식으로 web.xml에 필터 추가 하여 Spring security를 사용 할 수 있게 된다.

문제는 SpringMVC를 사용시에 문제가 되는데 

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
<-- 이건 RootWAC 설정 부분

 <servlet>
  <servlet-name>SpringMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>SpringMVC</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
<-- DispatcherServlet 설정 부분 

RootWAC를 등록하면 문제가 되지 않지만 이것을 등록하지 않고 DispatcherServlet만 사용하여 spring-mvc를 구성할 때에는 위에서 말한 DelegatingFilterProxy 기능을 사용할 수가 없게 되는것이 었다.

No WebApplicationContext found: no ContextLoaderListener registered?
<-- RootWAC를 등록하지 않고 DispatcherServlet구성만 사용 했을 때 DelegatingFilterProxy 을 사용 하고자하면 발생하는 에러

그래서 spring security 사용 할 때에는 RootWAC도 필요 없이도 생성 해줘야 하는 문제가 생겨 버렸다. 

프레임워크 구성시 사용 하다보면 이렇게 두개의 WAC가 생성이 되어 버리면 공통인 부분은 RootWAC로 빈을 등록 시키고  springMVC를 타는 웹 컨트롤 부분은 DispatcherServlet으로 등록 해줘야한다. 

문제는 이 부분에서 애매하게 되어 버리는 경우 개발하면서 많이 생기는데 ( ex: DispatcherServlet 부분에 등록된 빈을 RootWAC에 등록된 빈에서 불러온다거나.. 등등) 그 때마다 에러나면서 안되는 것을 보면서 DelegatingFilterProxy 등록 때문에 꼭 이렇게 나누어야 하는지 회의감이 들었다.

그래서 여기저기 찾아 보고 DelegatingFilterProxy 클래스 내부를 훝어본결과 해결 방법을 찾게되었다.

먼저 스프링에 대한 모든 유용한 정보(?)가 있다는  토비님 블로그에서 해답을 찾았는데 

일단 DelegatingFilterProxy 클래스 안에서 

 protected WebApplicationContext findWebApplicationContext() {
  if (this.webApplicationContext != null) {
   // the user has injected a context at construction time -> use it
   if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
    if (!((ConfigurableApplicationContext)this.webApplicationContext).isActive()) {
     // the context has not yet been refreshed -> do so before returning it
     ((ConfigurableApplicationContext)this.webApplicationContext).refresh();
    }
   }
   return this.webApplicationContext;
  }
  String attrName = getContextAttribute();
  if (attrName != null) {
   return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
  }
  else {
   return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  }
 }
<-- DelegatingFilterProxy.class 내부 내용 중 WAC를 찾는 메소드 부분

위의 매소드를 통하여 WAC를 찾고 있었다. 원래 기본 WebApplicationContextUtils.getWebApplicationContext(getServletContext()) 이 메소드를 통하여 찾아 올 수 있는 것은 RootWAC 밖에 없다고 한다. ( 자세한건 토비님 블로그 참조 )

그런데 거기서 DispatcherServlet 으로 등록한 WAC를 찾아 오기 위해서는 토비님 블로그에 자세하게 있지만 그대로 가져 오면 

FrameworkServlet.SERVLET_CONTEXT_PREFIX + 서블릿이름 

이 문자열을 WebApplicationContextUtils.getWebApplicationContext(getServletContext(), 이부분 );

이렇게 넘겨 주면 DispatcherServlet 으로 등록한 WAC를 가져 올 수 있다고 한다.

그래서 DelegatingFilterProxy.class 안을 더 확인 해본 결과 contextAttribute 속성에 그 문자열을 집어 넣으면 된다고 결론이 낫다.

FrameworkServlet.SERVLET_CONTEXT_PREFIX 이 상수 부분은 직접 사용 하지 못하여 내부 문자열을 확인 해본 결과"org.springframework.web.servlet.FrameworkServlet.CONTEXT." 이렇게 문자열을 가지고 있었다. 

그래서 서블릿이름과 조합해 본 결과 org.springframework.web.servlet.FrameworkServlet.CONTEXT.SpringMVC 이런식으로 매개 변수로 넘기면 될거라고 생각 했다.

 <filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  <init-param>
   <param-name>contextAttribute</param-name>
   <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.SpringMVC</param-value>
  </init-param>
 </filter>
<-- DelegatingFilterProxy 최종 설정 부분 

위의 설정을 저렇게 해주니 RootWAC 설정 부분을 제거 하고 DispatcherServlet 설정만 해주어도 에러 없이 실행이 잘 되었다.

물론 RootWAC를 사용 하는 사람은 상관없는 내용이지만 DispatcherServlet 하나만 사용하면서 Spring security 나 필터부분을 사용 하고자 하는 사람에게는 유용할것이라고 생각된다.

참고로 RootWAC를 가져 오기 위한 의 디폴트 속성은 
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 로 기술 되어 있었다 .

org.springframework.web.context.WebApplicationContext.ROOT 요부분이 넘어가는부분이 생략이 되어 있는듯하다.



출처 - http://sbcoba.tistory.com/8