[JAVA] Spring Security usage memo Basic / mechanism

Authentication / Authorization Story Remember-Me story CSRF story Session management story The story of the response header Method security story CORS story The story of Run-As The story of ACL Test story Talk about cooperation with MVC and Boot

Extra edition What Spring Security can and cannot do

environment

Java 1.8.0_xxx

AP server

Tomcat 8.0.35

Gradle 3.2.1

OS Windows 10

What is Spring Security?

One of the projects of Spring. A framework that primarily adds security features to web applications.

It provides protection against various well-known attacks such as CSRF and session fixation attacks.

Hello World Here is written in a little more order for presentation. It may be easier to understand if you read it together with the slides (I think that it was written only on the slides).

Implementation

Folder structure


|-build.gradle
`-src/main/webapp/
  |-index.jsp
  `-WEB-INF/
    |-applicationContext.xml
    `-web.xml

build.gradle


apply plugin: 'war'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava.options.encoding = 'UTF-8'

repositories {
    mavenCentral()
}

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    compile 'javax.servlet:jstl:1.2'
    compile 'org.springframework.security:spring-security-web:4.2.1.RELEASE'
    compile 'org.springframework.security:spring-security-config:4.2.1.RELEASE'
}

war.baseName = 'spring-security-sample'

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <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>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/security
         http://www.springframework.org/schema/security/spring-security.xsd">

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="hoge" password="HOGE" authorities="ROLE_USER" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

index.jsp


<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Hello Spring Security!!</title>
    </head>
    <body>
        <h1>Hello Spring Security!!</h1>
        
        <c:url var="logoutUrl" value="/logout" />
        <form action="${logoutUrl}" method="post">
            <input type="submit" value="logout" />
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        </form>
    </body>
</html>

Operation check

Generate a war file (spring-security-sample.war) with gradle war and deploy it to Tomcat.

After the deployment is complete, open http: // localhost: 8080 / spring-security-sample in your browser.

spring-security.jpg

A mysterious login screen is displayed.

Enter hoge and HOGE in User and Password, respectively, and click the Login button.

spring-security.jpg

ʻThe contents of index.jsp` are displayed.

Finally, click the logout button.

spring-security.jpg

Logout is complete and you are returned to the login screen.

Description

Dependencies

build.gradle


    compile 'org.springframework.security:spring-security-web:4.2.1.RELEASE'
    compile 'org.springframework.security:spring-security-config:4.2.1.RELEASE'

In the minimum configuration, you can add spring-security-web and spring-security-config as dependencies it seems good .RELEASE / reference / htmlsingle / #gradle).

spring-security-web

It contains code related to Filter and web apps. If you need web authentication or URL-based access control, you need this module. The main package is ʻorg.springframework.security.web`

spring-security-config

It contains code to parse the namespace used when writing definitions in xml and code for Java config. This module is required if you want to use XML-based settings or Java configs. The main package is ʻorg.springframework.security.config` The class included here is not directly used by the application.

Spring container initialization

web.xml


    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

By registering ContextLoaderListener as a listener, Spring container (ʻApplicationContext`) is initialized.

ʻIf you do not specify what to use for the ApplicationContext class, XmlWebApplicationContext is used by default. It is defined in ʻorg / springframework / web / context / ContextLoader.properties of spring-web-x.x.x.RELEASE.jar as follows, and ContextLoaderListener is loaded when the Servlet container is initialized.

ContextLoader.properties


# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

By default, XmlWebApplicationContext reads WEB-INF / applicationContext.xml as a configuration file.

Spring Security settings

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="hoge" password="HOGE" authorities="ROLE_USER" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

ʻApplicationContext.xml` itself is a standard configuration file for Spring bean definition, not a special configuration file for Spring Security.

By loading http://www.springframework.org/schema/security with xmlns, you can use tags for Spring Security (in the reference, use this tag dedicated to Spring Security * We call it * namespace **). You can use this tag to configure Spring Security settings on this file.

The above settings roughly define the following:

--Anyone can access / login (permitAll) --Access to all other paths (/ **) must be authenticated (ʻisAuthenticated ()) --Login method is Form login --Allow logout --Define a user with the role ROLE_USER with name = hoge, password = HOGE` as user information

The individual tags will be described in a little more detail below.


<http>

When you define the <http> tag, some beans are automatically registered in the container. Among them, the following two classes are important beans.

  1. FilterChainProxy
  2. SecurityFilterChain

FilterChainProxy is registered in the container with the bean name "springSecurityFilterChain". </ span> This class is the gateway to Spring Security processing.

SecurityFilterChain is just an interface in itself, and DefaultSecurityFilterChain is registered in the container as an implementation class. As the name suggests, SecurityFilterChain is a chain of javax.servlet.Filter that has a security function, and holds multiple Filters inside. Spring Security realizes the security function by using Filter.

As you can see from the name FilterChainProxy, the class itself does nothing. Specific processing is delegated to the Filters of the SecurityFilterChain.


<intercept-url>

Conditions (access control) required for access are defined for each URL pattern.

The pattern attribute can be described in ant path format.

In the ʻaccess attribute, specify the conditions required to access the URL specified in the patternattribute. permitAll means to allow all access (no authentication required). And ʻisAuthenticated () means to allow access if authenticated (logged in).

The ʻaccess` attribute is described using Spring's own expression language called Spring Expression Language (SpEL).


<form-login>

Defines that Form authentication is required.

In case of authentication error, you will be redirected to / login by default. By default, accessing / login will take you to a simple login page provided by Spring Security.

This default login page is generated by DefaultLoginPageGeneratingFilter in spring-security-web-x.x.x.RELEASE.jar.


<logout>

By adding this tag, you will be able to log out.

By default, a POST request to / logout will log you out.


<authentication-manager>

ʻDefine AuthenticationManager as a bean. ʻAuthenticationManager is a class that executes authentication processing and must be defined.

ʻAuthenticationManager itself is an interface, and the implementation class uses ProviderManager. ProviderManager itself does not perform specific authentication processing, but delegates the authentication processing to ʻAuthenticationProvider, which will be described later.


<authentication-provider>

ʻDefine AuthenticationProvider` as a bean. This interface provides a specific authentication process according to the type of authentication.

For example, the class that provides LDAP authentication processing is like LdapAuthenticationProvider.

If this tag is declared, DaoAuthenticationProvider will be registered in the container. This class gets user information from ʻUserDetailsService` and performs authentication processing.


<user-service>

ʻRegister UserDetailsService as a bean. This interface provides the ability to retrieve detailed user information (ʻUserDetails).

Declaring this tag will register ʻInMemoryUserDetailsManager` in the container. As the name implies, this class holds user information in memory. So to speak, it is a temporary implementation for checking the operation.


<user>

ʻDefine an instance of UserDetails`. This interface defines Getter methods and other methods for accessing detailed user information.

Declaring this tag creates an instance of the class ʻUser`.

The name, password, ʻauthorities` attributes allow you to specify names, passwords, and permissions to identify users.

Apply Spring Security to your application

web.xml


    <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>

Define DelegatingFilterProxy as a Servlet filter in web.xml. At this time, <filter-name> is defined with the name " springSecurityFilterChain ". Now Spring Security will be applied when the URL defined by <filter-mapping> is accessed.

DelegatingFilterProxy gets a bean that implements javax.servlet.Filter from the Spring container using the name set in its own <filter-name>. And it is a servlet filter that just delegates processing to that bean.

Here, " springSecurityFilterChain " is specified for <filter-name>. This name is the same as the FilterChainProxy bean name that is automatically registered in the container when you use the<http>tag in ʻapplicationContext.xml`. I am doing it.

In other words, DelegatingFilterProxy is responsible for bridging the Servlet filter and Spring Security (FilterChainProxy).

CSRF measures

index.jsp


<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Hello Spring Security!!</title>
    </head>
    <body>
        <h1>Hello Spring Security!!</h1>
        
        <c:url var="logoutUrl" value="/logout" />
        <form action="${logoutUrl}" method="post">
            <input type="submit" value="logout" />
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        </form>
    </body>
</html>

By default, CSRF measures are enabled. Therefore, when sending a request, it is necessary to pass a token for CSRF countermeasures together.

Token values and parameter names are stored in the request scope as _csrf.

A more detailed explanation of CSRF measures will be provided at a later date. .. ..

Summary

Summarize how the process is flowing behind the scenes between starting the server and logging in.

I'm using a sequence diagram, but it's not bad because it doesn't follow the strict UML notation for clarity.

Flow when starting the server

Although it is not strict in part, I think that the initialization → authentication process is performed in the following atmosphere.

SpringSecurity初期化時の動き.png

  1. The Servlet container is started and the ContextLoaderListener registered as Listener in web.xml is executed.
  2. An instance of XmlWebApplicationContext is created and /WEB-INF/applicationContext.xml is loaded.
  3. The <http> tag registers an instance of FilterChainProxy with the Spring container with the name "" springSecurityFilterChain ".
  4. The DelegateFilterProxy registered as Filter in web.xml is generated by the Servlet container.
  5. When requested, DelegateFilterProxy is called to get the bean from the Spring container with its own name (" springSecurityFilterChain ") (FilterChainProxy is obtained).
  6. Processing is delegated to FilterChainProxy.

Flow when accessing when not authenticated and being skipped to the login screen

It's also not strict, but I think the flow when redirecting to the login screen when accessing without logging in is as follows.

ログイン画面に飛ばされるときの流れ.png

  1. FilterSecurityInterceptor calls the authentication process of ʻAuthenticationManager`.
  2. ʻAuthenticationManager performs the authentication process, but throws ʻAuthenticationException because it is not authenticated.
  3. ʻExceptionTranslationFilter` catches the thrown exception.
  4. LoginUrlAuthenticationEntryPoint redirects you to the login screen.

Flow when actually logging in after accessing the login screen

The flow from immediately after being redirected to / login by LoginUrlAuthenticationEntryPoint to logging in by entering the user name and password.

ログイン実行時の流れ.png

  1. DefaultLoginPageGeneratingFilter will generate the default login screen.
  2. The user logs in.
  3. When there is a POST request in / login, ʻUsernamePasswordAuthenticationFilter delegates the authentication process to ʻAuthenticationManager.
  4. ProviderManager, which is an implementation class of ʻAuthenticationManager, delegates the authentication process to ʻAuthenticationProvider.
  5. DaoAuthenticationProvider, which is an implementation class of ʻAuthenticationProvider, acquires user information (ʻUserDetails) from ʻUserDetailsService` and executes the authentication process.
  6. If the authentication is successful, ʻUsernamePasswordAuthenticationFilter delegates the processing at the time of successful authentication to ʻAuthenticationSuccessFilter.
  7. ʻAuthenticationSuccessFilter implementation class SavedRequestAwareAuthenticationSuccessHandlerrestores and redirects the URL you were trying to access before you were taken to the login screen. --The URL you were accessing before logging in is stored inHttpSession`.

What happens if you don't use namespace

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="hoge" password="HOGE" authorities="ROLE_USER" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

The namespace (dedicated tag) automatically registers the various beans needed to run Spring Security in the container. Thanks to this, Spring Security settings are very simple to write.

However, when it becomes necessary to extend the authentication function, it becomes important to understand what kind of beans are registered behind the scenes and what kind of relationship they have.

To understand that, let's rewrite the configuration file created by Hello World without using namespace.

However, I will not do my best to completely match the setting in namespace, but to reproduce the series of login processes seen in Hello World. Therefore, some bean definitions are omitted.

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/util
         http://www.springframework.org/schema/util/spring-util-4.3.xsd">

    <bean id="springSecurityFilterChain"
          class="org.springframework.security.web.FilterChainProxy">
        <constructor-arg ref="securityFilterChain" />
    </bean>

    <bean id="securityFilterChain"
          class="org.springframework.security.web.DefaultSecurityFilterChain">
        <constructor-arg index="0">
            <bean class="org.springframework.security.web.util.matcher.AnyRequestMatcher" />
        </constructor-arg>

        <constructor-arg index="1">
            <list>
                <ref bean="securityContextPersistenceFilter" />
                <ref bean="csrfFilter" />
                <ref bean="logoutFilter" />
                <ref bean="usernamePasswordAuthenticationFilter" />
                <ref bean="defaultLoginPageGeneratingFilter" />
                <ref bean="exceptionTranslationFilter" />
                <ref bean="filterSecurityInterceptor" />
            </list>
        </constructor-arg>
    </bean>

    <!--Initialize / clear SecurityContextHolder-->
    <bean id="securityContextPersistenceFilter"
          class="org.springframework.security.web.context.SecurityContextPersistenceFilter" />

    <!-- CSRF -->
    <bean id="csrfFilter"
          class="org.springframework.security.web.csrf.CsrfFilter">
        <constructor-arg ref="csrfTokenRepository" />
    </bean>

    <bean id="csrfTokenRepository"
          class="org.springframework.security.web.csrf.LazyCsrfTokenRepository">
        <constructor-arg>
            <bean class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository" />
        </constructor-arg>
    </bean>

    <!--Log out-->
    <bean id="logoutFilter"
          class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <constructor-arg index="0" value="/login?logout" />
        <constructor-arg index="1">
            <list>
                <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
                <bean class="org.springframework.security.web.csrf.CsrfLogoutHandler">
                    <constructor-arg ref="csrfTokenRepository" />
                </bean>
            </list>
        </constructor-arg>
    </bean>

    <!--Login authentication-->
    <bean id="usernamePasswordAuthenticationFilter"
          class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="authenticationFailureHandler">
            <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
                <property name="defaultFailureUrl" value="/login?error" />
            </bean>
        </property>
    </bean>

    <!--Default login page generation-->
    <bean id="defaultLoginPageGeneratingFilter"
          class="org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter">
        <constructor-arg ref="usernamePasswordAuthenticationFilter" />
        <property name="authenticationUrl" value="/login" />
    </bean>

    <!--Authentication exception handler-->
    <bean id="exceptionTranslationFilter"
          class="org.springframework.security.web.access.ExceptionTranslationFilter">
        <constructor-arg>
            <bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
                <constructor-arg value="/login" />
            </bean>
        </constructor-arg>
    </bean>

    <!--Authentication / authorization processing-->
    <bean id="filterSecurityInterceptor"
          class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
        <property name="securityMetadataSource" ref="securityMetadataSource" />
    </bean>
    
    <!--Authentication processing body-->
    <bean id="authenticationManager"
          class="org.springframework.security.authentication.ProviderManager">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
                    <property name="userDetailsService">
                        <bean class="org.springframework.security.provisioning.InMemoryUserDetailsManager">
                            <constructor-arg>
                                <list>
                                    <bean class="org.springframework.security.core.userdetails.User">
                                        <constructor-arg index="0" value="hoge" />
                                        <constructor-arg index="1" value="HOGE" />
                                        <constructor-arg index="2">
                                            <bean class="org.springframework.security.core.authority.AuthorityUtils"
                                                  factory-method="commaSeparatedStringToAuthorityList">
                                                <constructor-arg value="ROLE_USER" />
                                            </bean>
                                        </constructor-arg>
                                    </bean>
                                </list>
                            </constructor-arg>
                        </bean>
                    </property>
                </bean>
            </list>
        </constructor-arg>
    </bean>

    <!--Authorization processing body-->
    <bean id="accessDecisionManager"
          class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter" />
            </list>
        </constructor-arg>
    </bean>

    <!--Definition information such as URLs that require authentication and permission mappings-->
    <bean id="securityMetadataSource"
          class="org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource">
        <constructor-arg index="0">
            <util:map id="requestMap" map-class="java.util.LinkedHashMap">
                <entry>
                    <key>
                        <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
                            <constructor-arg value="/login" />
                        </bean>
                    </key>
                    <bean class="org.springframework.security.access.SecurityConfig"
                          factory-method="createList">
                        <constructor-arg value="permitAll" />
                    </bean>
                </entry>
                <entry>
                    <key>
                        <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
                            <constructor-arg value="/**" />
                        </bean>
                    </key>
                    <bean class="org.springframework.security.access.SecurityConfig"
                          factory-method="createList">
                        <constructor-arg value="isAuthenticated()" />
                    </bean>
                </entry>
            </util:map>
        </constructor-arg>
        <constructor-arg index="1">
            <bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />
        </constructor-arg>
    </bean>
</beans>

The class diagram looks like the following.

HelloWorldの仕組み.png

To put it a little more roughly,

spring-security.jpg

Like this.

With FilterChainProxy as the entrance, various Filters grouped by SecurityFilterChain are executed in order, and FilterSecurityInterceptor performs authentication / authorization processing just before the protected object.

Secure object

When I read the Spring Security reference, I often come across the term ** secure object **.

This is a term defined by Spring Security and refers to what is (or should be) secured.

Typical examples are "Web request" and "method execution".

Is FilterSecurityInterceptor a Filter or an Interceptor?

When I first saw the name of FilterSecurityInterceptor, I was confused as to whether this was Filter or ʻInterceptor`.

This class implements the Filter interface, so it looks like Filter at first glance.

The answer, or rather, when I looked at the type hierarchy of this class, I came to know the true nature of this class.

FilterSecurityIntercpetorのクラス階層.png

ʻMethodSecurityInterceptor, which has AbstractSecurityInterceptor as the same parent, exists as a sibling class.

That is, ʻInterceptor for the security of FilterisFilterSecurityInterceptor, Since ʻInterceptor for the security of method execution is MethodSecurityInterceptor, these are just ʻInterceptor`.

The FilterSecurityInterceptor is, so to speak," the Interceptor for the Filter by the Filter of the Filter". maybe.

Java Configuration Starting with Spring Security 3.2, Java Configuration added in Spring 3.1 is available. Java Configuration allows you to write namespace-like settings without an XML file.

Using Java Configuration has the advantage that the compiler will check for some configuration mistakes and refactoring will be easier.

Replace Hello World with Java Configuration

Implementation

Folder structure


|-build.gradle
`-src/main/
  |-java/
  |  `-sample/spring/security/
  |    `-MySpringSecurityConfig.java
  `-webapp/
    |-index.jsp
    `-WEB-INF/
      `-web.xml

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    
    <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>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>sample.spring.security.MySpringSecurityConfig</param-value>
    </context-param>
</web-app>

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("hoge").password("HOGE").roles("USER");
    }
}

ʻIndex.jsp` has not changed.

Description

Change container implementation

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
    
    <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>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>sample.spring.security.MySpringSecurityConfig</param-value>
    </context-param>
</web-app>

Change the setting to use ʻAnnotationConfigWebApplicationContext instead of XmlWebApplicationContext`, which was used by default.

If you define <context-param> with the name contextClass, ContextLoaderListener will use the class specified there as a container.

Furthermore, the configuration class read by ʻAnnotationConfigWebApplicationContext can be specified by defining with the name contextConfigLocation`.

Enable Spring Security

MySpringSecurityConfig.java


package sample.spring.security;

...
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    ...
}

To enable Java Configuration for Spring Security, annotate the class loaded with ʻAnnotationConfigWebApplicationContext with @EnableWebSecurity`.

You can see why Spring Security is enabled by looking at the implementation of this annotation.

EnableWebSecurity.java


...
package org.springframework.security.config.annotation.web.configuration;

...

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;

...
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
...
}

WebSecurityConfiguration is loaded using @ Import. This class is also a configuration class annotated with @Configuration. </ span>

WebSecurityConfiguration.java


...
package org.springframework.security.config.annotation.web.configuration;
...

import javax.servlet.Filter;

...
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
	...

	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		...
		return webSecurity.build();
	}

	...
}

ʻThe value of AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME is "springSecurityFilterChain" . The return value of webSecurity.build ()isFilterChainProxy`, and the bean is registered in the same way as it was done in xml.

So, annotate with @EnableWebSecurity to enable Spring Security.

Spring Security settings

MySpringSecurityConfig.java


package sample.spring.security;

...
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();
    }

    ...
}

To make specific settings for Spring Security, first inherit the class WebSecurityConfigurerAdapter.

The WebSecurityConfigurerAdapter is designed to build a simple SecurityFilterChain by default. By inheriting this class and overriding methods such as configure (HttpSecurity), you can customize the SecurityFilterChain to be built.

The HttpSecurity received as an argument corresponds to the<http>tag in namespace. Basically, the settings made with <http> can also be made with HttpSecurity.

The above implementation has the same meaning as the XML below. (* Logout is omitted because WebSecurityConfigurerAdapter is automatically registered internally.)

applicaitonContext.xml


    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

ʻThe authorizeRequests ()method is the method that initiates the setting of in the namespace. The return type of the method has been replaced by a class called ʻExpressionInterceptUrlRegistry, and a dedicated setting method can be called.

If you want to finish the setting of <intercept-url> and continue another setting, insert the ʻand () method. The ʻand () method returns HttpSecurity, so you can continue with the next configuration without breaking the method chain.

formLogin (), as the name implies, enables Form authentication.

Definition of user information

MySpringSecurityConfig.java


package sample.spring.security;

...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("hoge").password("HOGE").roles("USER");
    }
}

ʻAuthenticationManagerBuilder is a support class for defining the following classes of ʻAuthenticationManager, and you can define ʻUserDetailsService` etc. using the method chain as in the above implementation.

By the way, you may declare the method that defines the bean directly without using ʻAuthenticationManagerBuilder`.

When setting in Bean definition


package sample.spring.security;

import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    ...
    
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("hoge").password("HOGE").roles("USER").build());
        return manager;
    }
}

Java Configuration initialization flow

Roughly, it seems that it is initialized in the following flow.

JavaConfigの初期化.png

Do not use web.xml

If your AP server is Servlet 3.0 or above, you can start using Spring Security without web.xml.

Implementation

Folder structure


|-build.gradle
`-src/main/
  |-java/
  |  `-sample/spring/security/
  |    |-MySpringSecurityInitializer.java
  |    `-MySpringSecurityConfig.java
  `-web/
    `-index.jsp

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 and ʻindex.jsp` are the same as the previous ones, so I will omit them.

Description

I've removed web.xml and added a class called MySpringSecurityInitializer instead.

This class is created by inheriting from ʻAbstractSecurityWebApplicationInitializer. Then, implement to pass the Class object of the Java Configuration class (MySpringSecurityConfig`) to the parent class in the constructor.

This eliminates all the settings that were done in web.xml.

How it works

Several mechanisms added in Servlet 3.0 are used to implement this functionality. Let's see how the setting is realized without web.xml.

ServletContainerInitializer ʻAbstractSecurityWebApplicationInitializer implements an interface called WebApplicationInitializer. The Javadoc for this WebApplicationInitializer` states:

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 {

It seems that the class that implements this interface is automatically loaded by the class SpringServletContainerInitializer in the environment of Servlet 3.0 or higher.

Go see the implementation of SpringServletContainerInitializer.

SpringServletContainerInitializer.java


package org.springframework.web;

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

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

It implements ServletContainerInitializer and is annotated with @HandlesTypes. Both were added in Servlet 3.0.

When the Servlet container starts, the ʻonStartup (Set <Class <? >>, ServletContext) method of this class is executed. In the first argument of the method, the container automatically searches for the [^ 1] class related to Class specified by @HandlesTypesand passes it. In the case ofSpringServletContainerInitializer, WebApplicationInitializer` is specified, so the class that implements this interface is searched by the Servlet container and passed.

[^ 1]: Implementation class for interface, its class and subclass for abstract / concrete class, and annotated class for annotation.

When the ʻonStartup ()method ofSpringServletContainerInitializer is executed, an instance of the class that implements WebApplicationInitializeris created and the ʻonStartup (ServletContext)method of WebApplicationInitializer is executed.

AbstractSecurityWebApplicationInitializer The ʻonStartup (ServletContext) method of ʻAbstractSecurityWebApplicationInitializer has the following implementation.

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);
	}

        ...
}

There are some familiar classes here and there ...

In short, the settings that were done in web.xml are done in this implementation.

Summary

It is initialized by the following flow. (As usual, there are some parts that are not strict for simplification)

Servlet3.0での初期化の流れ.png

reference

Recommended Posts

Spring Security usage memo Basic / mechanism
Spring Security usage memo CSRF
Spring Security usage memo Run-As
Spring Security Usage memo Method security
Spring Security usage memo Remember-Me
Spring Security usage memo CORS
Spring Security usage memo test
Spring Security usage memo session management
Spring Security Usage Memo Domain Object Security (ACL)
Spring Security usage memo: Cooperation with Spring MVC and Boot
Achieve BASIC authentication with Spring Boot + Spring Security
Spring retrospective memo
JavaParser usage memo
WatchService usage memo
PlantUML usage memo
JUnit5 usage memo
About Spring Security authentication
java basic knowledge memo
ruby basic syntax memo
Spring Security causes 403 forbidden
Java learning memo (basic)
Spring boot memo writing (2)
[Personal memo] About Spring framework
JJUG CCC 2018 Spring participation memo
Spring Framework self-study memo series_1
Basic usage notes for Jackson
swift CollectionView Super basic usage
[Java] Thymeleaf Basic (Spring Boot)
Login function with Spring Security
[Spring Security] Spring Security on GAE (SE)
Dependency Management Plugin Usage memo
Try using Spring Boot Security
Spring boot controller method memo