Spring Security | Spring Security이란? | web.xml 없이


AP 서버가 Servlet 3.0 이상이면 web.xml 없이도 Spring Security를 사용할 수도 있다.

.
├── build.gradle
└── src
    └── main
        ├── java
        │   └── sample
        │       └── spring
        │           └── security
        │               ├── MySpringSecurityConfig.java
        │               └── MySpringSecurityInitializer.java
        └── webapp
            └── index.jsp

src/main/java/sample/spring/security/MySpringSecurityInitializer.java

package sample.spring.security;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class MySpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
    public MySpringSecurityInitializer() {
        super(MySpringSecurityConfig.class);
    }
}

(MySpringSecurityConfig.java과 index.jsp는 이전과 동일하므로 생략한다.)

web.xml을 제거하고 대신 MySpringSecurityInitializer라는 클래스를 추가하였다.

이 클래스는 AbstractSecurityWebApplicationInitializer을 상속받아 작성하였다. 그리고 생성자에서 Java Configuration 클래스(MySpringSecurityConfig)의 Class 객체를 부모 클래스에 전달하도록 구현한다.

이렇게 하여 web.xml에서 있었던 있던 설정이 모두 불필요하게 된다.

구조

이 기능의 구현은 Servlet 3.0에 추가된 몇 가지 방법이 이용되고 있다. 어떻게 하여 web.xml 없이 설정이 가능한지, 그 구조에 대해 설명하겠다.

ServletContainerInitializer

AbstractSecurityWebApplicationInitializer는 WebApplicationInitializer라는 인터페이스를 구현하고 있다. 이 WebApplicationInitializer의 Javadoc에는 다음과 같이 나와있다.

WebApplicationInitializer.java

/**
 * ...
 *
 * <p>Implementations of this SPI will be detected automatically by {@link
 * SpringServletContainerInitializer}, which itself is bootstrapped automatically
 * by any Servlet 3.0 container. See {@linkplain SpringServletContainerInitializer its
 * Javadoc} for details on this bootstrapping mechanism.
 * ...
 */
public interface WebApplicationInitializer {

이 인터페이스를 구현한 클래스는 Servlet 3.0 이상의 환경이라면, SpringServletContainerInitializer라는 클래스에 의해 자동으로 로드하게 되는 것이다.

SpringServletContainerInitializer 구현을 살펴 보도록 하자.

SpringServletContainerInitializer.java
package org.springframework.web;

...
import javax.servlet.ServletContainerInitializer;
import javax.servlet.annotation.HandlesTypes;

...
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
   ...

ServletContainerInitializer를 구현하고, @HandlesTypes으로 주석되어 있다. 모두 Servlet 3.0에서 추가되어 있다.

Servlet 컨테이너가 시작할 때에 이 클래스의 onStartup(Set<Class<?>>, ServletContext) 메소드가 실행된다. 메소드의 첫번째 인수는 @HandlesTypes에서 지정된 Class에 관련된 클래스가 컨테이너에 의해 자동으로 검색되어 전달된다. SpringServletContainerInitializer의 경우 WebApplicationInitializer가 지정되어 있기 때문에, 이 인터페이스를 구현한 클래스가 Servlet 컨테이너에 의해 검색되어 넘어 온다.

SpringServletContainerInitializer의 onStartup() 메소드가 실행되면 WebApplicationInitializer를 구현한 클래스의 인스턴스를 생성하여 WebApplicationInitializer의 onStartup(ServletContext) 메소드를 실행한다.

AbstractSecurityWebApplicationInitializer

AbstractSecurityWebApplicationInitializer의 onStartup(ServletContext)메소드는 다음과 같이 구현되어 있다.

AbstractSecurityWebApplicationInitializer.java

package org.springframework.security.web.context;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.ServletContext;
...

import org.springframework.context.ApplicationContext;
...
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;

...
public abstract class AbstractSecurityWebApplicationInitializer
        implements WebApplicationInitializer {

    ...

    public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

    ...

    public final void onStartup(ServletContext servletContext) throws ServletException {
        beforeSpringSecurityFilterChain(servletContext);
        if (this.configurationClasses != null) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); 
            rootAppContext.register(this.configurationClasses);
            servletContext.addListener(new ContextLoaderListener(rootAppContext)); 
        }
        if (enableHttpSessionEventPublisher()) {
            servletContext.addListener(
                    "org.springframework.security.web.session.HttpSessionEventPublisher");
        }
        servletContext.setSessionTrackingModes(getSessionTrackingModes());
        insertSpringSecurityFilterChain(servletContext);
        afterSpringSecurityFilterChain(servletContext);
    }

    ...

    private void insertSpringSecurityFilterChain(ServletContext servletContext) {
        String filterName = DEFAULT_FILTER_NAME;
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
                filterName); 
        String contextAttribute = getWebApplicationContextAttribute();
        if (contextAttribute != null) {
            springSecurityFilterChain.setContextAttribute(contextAttribute);
        }
        registerFilter(servletContext, true, filterName, springSecurityFilterChain);
    }

        ...
}

무엇인가 본 기억이 있는 클래스가 보일 것이다.

요점은 web.xml에서하던 설정이 구현에서 이루어지고 있다.