[JAVA] Spring Security usage memo Authentication / authorization

Basic / mechanism 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

Authentication

UserDetailsService: Search user information

ʻUserDetailsService` is responsible for retrieving user information.

Spring Security provides several classes that implement ʻUserDetailsService`.

In-memory

Implementation that saves user information in memory. The specific class is ʻInMemoryUserDetailsManager`.

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"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       ...>

    ...

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

--Declare using the <user-service> and <user> tags.

Java Configuration

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 {
        ...
    }

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

--Declare a method that receives ʻAuthenticationManagerBuilder in the configuration class and annotate it with @Autowired. --ʻInMemoryAuthentication () Set the definition information with the method.

JDBC Implementation to get user information from database. The actual class will be JdbcUserDetailsManager.

** Common **

build.gradle (additional dependencies)


    compile 'org.springframework:spring-jdbc:4.3.6.RELEASE'
    compile 'com.h2database:h2:1.4.193'

--For verification, use H2 for DB in embedded mode. --Also, spring-jdbc is added to the dependency to create DataSource.

src/main/resources/sql/create_database.sql


CREATE TABLE USERS (
    USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
    PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
    ENABLED  BOOLEAN NOT NULL
);

CREATE TABLE AUTHORITIES (
    USERNAME  VARCHAR_IGNORECASE(50) NOT NULL,
    AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,
    CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY (USERNAME) REFERENCES USERS(USERNAME),
    CONSTRAINT UK_AUTHORITIES UNIQUE (USERNAME, AUTHORITY)
);

INSERT INTO USERS VALUES ('fuga', 'FUGA', true);
INSERT INTO AUTHORITIES VALUES ('fuga', 'USER');

--By default, if you declare a table column as described above, user information will be searched automatically. --It can be changed by setting. --Files should be deployed under the classpath (will be used later as an initialization script when creating a data source)

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"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="
         ...
         http://www.springframework.org/schema/jdbc
         http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">

    ...
    
    <jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:/sql/create_database.sql" />
    </jdbc:embedded-database>
    
    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:jdbc-user-service data-source-ref="dataSource"  />
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

--Use the <jdbc-user-service> tag. --In the data source definition, the initialization script written above is executed using the <jdbc: script> tag.

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
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;

import javax.sql.DataSource;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ...
    }
    
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.H2)
                .setScriptEncoding("UTF-8")
                .addScript("/sql/create_database.sql")
                .build();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource);
    }
}

--Use the jdbcAuthentication () method of ʻAuthenticationManagerBuilder`.

Adjust table and column names

CREATE TABLE USERS (
    LOGIN_ID VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
    PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
    ENABLED  BOOLEAN NOT NULL
);

CREATE TABLE AUTHORITIES (
    LOGIN_ID  VARCHAR_IGNORECASE(50) NOT NULL,
    ROLE VARCHAR_IGNORECASE(50) NOT NULL,
    CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY (LOGIN_ID) REFERENCES USERS(LOGIN_ID),
    CONSTRAINT UK_AUTHORITIES UNIQUE (LOGIN_ID, ROLE)
);

Try changing ʻUSERNAME to LOGIN_ID and ʻAUTHORITY to ROLE.

If you want to change the table name or column name to any one, adjust the SQL when searching for user information. The column name can be anything as long as the order of the items at the time of search matches.

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"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       ...>

    ...
    
    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:jdbc-user-service
                data-source-ref="dataSource"
                users-by-username-query="SELECT LOGIN_ID, PASSWORD, ENABLED FROM USERS WHERE LOGIN_ID=?"
                authorities-by-username-query="SELECT LOGIN_ID, ROLE FROM AUTHORITIES WHERE LOGIN_ID=?" />
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

--Define a query to search for ʻUSERNAME, PASSWORD, ʻENABLED in the ʻusers-by-username-query attribute. --Define a query to search for ʻUSERNAME, ʻAUTHORITY in the ʻauthorities-by-username-query attribute.

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
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;

import javax.sql.DataSource;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ...
    }
    
    @Bean
    public DataSource dataSource() {
        ...
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
        .usersByUsernameQuery("SELECT LOGIN_ID, PASSWORD, ENABLED FROM USERS WHERE LOGIN_ID=?")
        .authoritiesByUsernameQuery("SELECT LOGIN_ID, ROLE FROM AUTHORITIES WHERE LOGIN_ID=?");
    }
}

--Define a query to search for ʻUSERNAME, PASSWORD, ʻENABLED in the ʻusersByUsernameQuery (String) method. --Define a query to search for ʻUSERNAME, ʻAUTHORITY in the ʻauthoritiesByUsernameQuery (String) method.

Create your own UserDetailsService

MyUserDetailsService.java


package sample.spring.security;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if ("hoge".equals(username)) {
            return new User(username, "HOGE", AuthorityUtils.createAuthorityList("USER"));
        }
        
        throw new UsernameNotFoundException("not found : " + username);
    }
}

--ʻCreate a class that implements UserDetailsService. --ʻUserDetailsService has only one method called loadUserByUsername (String). --Since a character string for identifying the user (usually the user name entered on the login screen etc.) is passed as an argument, the user information corresponding to the identification character string is returned. --Throw ʻUsernameNotFoundException if the corresponding user information does not exist. --User information is created by a class that implements the ʻUserDetails interface. --A class called ʻUser is prepared as standard. --If there are various problems with the ʻUser class due to adult circumstances, you can create your own class that implements ʻUserDetails`.

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

    ...

    <bean id="myUserDetailsService" class="sample.spring.security.MyUserDetailsService" />
    
    <sec:authentication-manager>
        <sec:authentication-provider user-service-ref="myUserDetailsService" />
    </sec:authentication-manager>
</beans>

--Specify ʻUserDetailsService created with the ʻuser-service-ref attribute of<authentication-provider>.

Java Configuration

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 {

    ...
    
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new MyUserDetailsService());
    }
}

--Register ʻUserDetailsService with the ʻuserDetailsService () method of ʻAuthenticationManagerBuilder`.

Password hashing

Why hashing is needed

In the unlikely event that data is leaked, it is ill-conditioned that the password is stored in clear text.

Therefore, the password is usually saved in a database or the like in a form in which the original character string cannot be specified. If you do not need to combine passwords, use a hash function to convert the password.

BCrypt There are MD5 and SHA as hash functions, but BCrypt seems to be better if you want to hash passwords.

If you simply apply the hash function once, [password attacks] such as brute force attacks, dictionary attacks, and rainbow tables (https://ja.wikipedia.org/wiki/%E3%83%91%E3%) 82% B9% E3% 83% AF% E3% 83% BC% E3% 83% 89% E3% 82% AF% E3% 83% A9% E3% 83% 83% E3% 82% AF) become.

BCrypt does not simply hash the input once, but adds a random salt and then applies the hash multiple times, making it harder to guess the original password.

Spring Security also recommends using this BCrypt.

Implementation

Password hashing can be achieved by setting PasswordEncoder to DaoAuthenticationProvider. If you use BCrypt, use the PasswordEncoder implementation BCryptPasswordEncoder`.

In addition, the hash value is described in advance by BCrypt hash value calculation | tekboy.

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

    ...
    
    <bean id="passwordEncoder"
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
    
    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user
                    name="hoge"
                    password="$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S"
                    authorities="ROLE_USER" />
            </sec:user-service>
            
            <sec:password-encoder ref="passwordEncoder" />
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

--Define BCryptPasswordEncoder as a bean and specify it in the ref attribute of <password-encoder>. --Set <password-encoder> as a child element of <authentication-provider>.

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    ...
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception {
        auth.inMemoryAuthentication()
            .passwordEncoder(passwordEncoder)
            .withUser("hoge")
            .password("$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S")
            .roles("USER");
    }
}

-Register PasswordEncoder with thepasswordEncoder ()method of ʻAbstractDaoAuthenticationConfigurer. --ʻAbstractDaoAuthenticationConfigurer is the parent class of ʻInMemoryUserDetailsManagerConfigurer, which is the return type of the ʻinMemoryAuthentication () method of ʻAuthenticationManagerBuilder`.

Encode password

To encode any password, just fetch PasswordEncoder from the container and use the ʻencode ()` method.

EncodePasswordServlet.java


package sample.spring.security.servlet;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/encode-password")
public class EncodePasswordServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
        PasswordEncoder passwordEncoder = context.getBean(PasswordEncoder.class);

        String password = req.getParameter("password");
        String encode = passwordEncoder.encode(password);
        System.out.println(encode);
    }
}

After logging in, access http: // localhost: 8080 / xxxx / encode-password? password = fuga (replace the context path as appropriate).

Server console output


$2a$10$2qVDkAqxp8eDrxR8Be2ZpubYGOCVZ7Qy9uK/XzOIY1ZoxpChtrWDK

Authentication ʻAuthentication` exists in one of the important classes that make up Spring Security. This class stores information about authenticated users (such as user names and a list of privileges granted).

If authentication by ʻAuthenticationProvider is successful, ʻAuthenticationProvider creates and returns an authenticated ʻAuthentication object (ʻisAuthenticated () returns true). After that, this authenticated ʻAuthentication` object will be referenced in subsequent processing such as authorization check.

How to get Authentication and available information

SecurityContextHolderSampleServlet.java


package sample.spring.security.servlet;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static java.util.stream.Collectors.*;

@WebServlet("/authentication")
public class SecurityContextHolderSampleServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("[authorities]");
        System.out.println("  " + auth.getAuthorities().stream()
                                    .map(GrantedAuthority::getAuthority)
                                    .collect(joining("\n  ")));

        WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails();
        System.out.println("[details]");
        System.out.println("  IP Address : " + details.getRemoteAddress());
        System.out.println("  Session ID : " + details.getSessionId());

        UserDetails principal = (UserDetails) auth.getPrincipal();
        System.out.println("[principal]");
        System.out.println("  username : " + principal.getUsername());
        System.out.println("  password : " + principal.getPassword());

        System.out.println("[credentials]");
        System.out.println("  " + auth.getCredentials());
    }
}

Execution result


[authorities]
  USER
[details]
  IP Address : 0:0:0:0:0:0:0:1
  Session ID : 0225051BCDFE2C34D55DF4FA9D9685C2
[principal]
  username : hoge
  password : null
[credentials]
  null

--To get ʻAuthentication associated with the current request, use SecurityContextHolder.getContext (). GetAuthentication () . --SecurityContextHolder.getContext ()returns theSecurityContextassociated with the current request. --You can get the privileges granted to the current logged-in user (collection ofGrantedAuthority) with ʻAuthentication.getAuthorities (). --ʻAuthentication.getDetails () returns WebAuthenticationDetails by default. --You can get the IP address and session ID from this class. --You can get the logged-in user's ʻUserDetails with ʻAuthentication.getPrincipal (). --ʻAuthentication.getCredentials () returns the information used for user authentication (usually the login password) by design.

For security, password information is explicitly cleared (set to null) by ʻAuthenticationManager (ProviderManager) after a successful login. It is also possible not to erase it with the option (ʻeraseCredentialsAfterAuthentication).

Authentication storage location

Authenticationの保存場所.png

The SecurityContext that holds ʻAuthentication is saved in HttpSession` by default after authentication is completed.

The next time it is accessed in the same session, the SecurityContext stored in HttpSession will be retrieved and stored in SecurityContextHolder.

This SecurityContextHolder uses ThreadLocal by default to store SecurityContext in the static field. Therefore, the SecurityContext associated with the current request can be obtained globally.

Strictly speaking, SecurityContextHolder holds an instance of SecurityContextHolderStrategy, and the implementation of this interface is ThreadLocalSecurityContextHolderStrategy, which stores SecurityContext in ThreadLocal.

By the way, SecurityContextPersistenceFilter does the loading and unloading of SecurityContext to SecurityContextHolder and the clearing of ThreadLocal for each request.

Saving method other than ThreadLocal

In addition to ThreadLocalSecurityContextHolderStrategy, there are two remaining classes in the implementation of SecurityContextHolderStrategy.

GlobalSecurityContextHolderStrategy

Save only one SecurityContext in your application.

This class is used, for example, in a stand-alone application. For standalone apps, the running user may be the only user. In that case, even if you create multiple threads, you may have to share the same credentials for all of them.

InheritableThreadLocalSecurityContextHolderStrategy

When a new thread is created, it shares the SecurityContext of the parent thread.

This class may be used by multiple users, but it is used when a new thread is created in the processing of each user and background processing is executed. In that case, the threads are separated, but the authentication information must inherit the user information of the parent thread.

Which storage method is used can be changed by any of the following methods.

** Specified in system properties **

It can be specified by passing the system property spring.security.strategy to the JVM startup options.

When specifying when starting Tomcat


> set JAVA_OPTS=-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

> cd %TOMCAT_HOME%\bin

> startup.bat

** Specify with static method **

python


SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

SecurityContextHolder provides a method calledsetStrategyName (String). For the argument, specify the constant defined in SecurityContextHolder as well.

Specify the login page

If you enable Form login but do not specify a login page, the default is a simple login page generated by Spring Security.

spring-security.jpg

This is just a screen for checking the operation, and in the actual application, it is necessary to replace it with the login page created by yourself.

Change the login page as follows.

Implementation

my-login.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>My Login Page</title>
    </head>
    <body>
        <h1>My Login Page</h1>
        
        <c:url var="loginUrl" value="/login" />
        <form action="${loginUrl}" method="post">
            <div>
                <label>username: <input type="text" name="username" /></label>
            </div>
            <div>
                <label>password: <input type="password" name="password" /></label>
            </div>
            <input type="submit" value="login" />
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        </form>
    </body>
</html>

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="/my-login.jsp" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login login-page="/my-login.jsp" />
        <sec:logout />
    </sec:http>

    ...
</beans>

Java Configuration

MySpringSecurityConfig.java


...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/my-login.jsp").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/my-login.jsp");
    }

    ...
}

Operation check

spring-security.jpg

If you try to access any page, you will be taken to your own login page.

Description

applicationContext.xml


        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/my-login.jsp" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login login-page="/my-login.jsp" />

--You must be able to log in to your own login page even if you are not authenticated, so make it accessible with permitAll. --Specify the path of your own login page in the login-page attribute of the<form-login>tag. --For Java Configuration, the .loginPage (String) method.

my-login.jsp


        <c:url var="loginUrl" value="/login" />
        <form action="${loginUrl}" method="post">
            <div>
                <label>username: <input type="text" name="username" /></label>
            </div>
            <div>
                <label>password: <input type="password" name="password" /></label>
            </div>
            <input type="submit" value="login" />
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        </form>

--Login requests can be made to / login with the POST method. --The login path can be changed with login-processing-url of<form-login>. --For Java Configuration, the loginProcessingUrl (String) method. --The user name should be ʻusernameand the password should bepassword. --Each parameter name can be changed with the ʻusername-parameter and password-parameter attributes of <form-login> --In Java Configuration, it can be changed with ʻusernameParameter (String), passwordParameter (String)`

Specify the page after login

By default, the transition destination after login is controlled as follows.

  1. If there is a URL you were accessing before logging in, redirect to it
  2. If you open the login page directly and log in, redirect to the application root (/) (default target)

Implementation

hello.html


<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello!!</h1>
    </body>
</html>

Add a simple screen.

Operation check

spring-security.jpg

Access /hello.html.

spring-security.jpg

You will be taken to the login screen, so log in.

spring-security.jpg

You will be taken to the page (/hello.html) that you were accessing before logging in.

Then, log out once and then access the login page (/ login) directly.

spring-security.jpg

log in.

spring-security.jpg

It is skipped to the application root (/).

Change the default target

You can change the transition destination after opening the login page directly and logging in with default-target-url.

Implementation

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 default-target-url="/hello.html" />
        <sec:logout />
    </sec:http>

    ...
</beans>

--Specified by default-target-url

Java Configuration

MySpringSecurityConfig.java


...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().defaultSuccessUrl("/hello.html");
    }

    ...
}

--Specified by defaultSuccessUrl (String)

Operation check

Access the login screen (/ login) directly.

spring-security.jpg

log in

spring-security.jpg

You will be skipped to the URL (/hello.html) specified by default-target-url.

Always transition to the default target

Even if there is a page you tried to access before logging in, change it so that it will transition to the default target after logging in.

Implementation

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 default-target-url="/hello.html"
                        always-use-default-target="true" />
        <sec:logout />
    </sec:http>

    ...
</beans>

--Specify true for ʻalways-use-default-target`

Java Configuration

MySpringSecurityConfig


...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().defaultSuccessUrl("/hello.html", true);
    }

    ...
}

--Specify true as the second argument ofdefaultSuccessUrl (String, boolean)

Operation check

spring-security.jpg

Access the appropriate URL.

spring-security.jpg

You will be taken to the login screen, so log in.

spring-security.jpg

You will be skipped to the page specified by default-target-url.

Implement the process when login is successful

Implementation

MyAuthenticationSuccessHandler.java


package sample.spring.security.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
                throws IOException, ServletException {
        RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        redirectStrategy.sendRedirect(request, response, "/hello.html");
    }
}

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"
       ...>
    
    <bean id="authenticationSuccessHandler"
          class="sample.spring.security.handler.MyAuthenticationSuccessHandler" />
    
    <sec:http>
        ...
        <sec:form-login authentication-success-handler-ref="authenticationSuccessHandler" />
        ...
    </sec:http>

    ...
</beans>

--Specify the bean of the created ʻAuthenticationSuccessHandler in the ʻauthentication-success-handler-ref attribute of the<form-login>tag.

Java Configuration

MySpringSecurityConfig.java


...

import sample.spring.security.handler.MyAuthenticationSuccessHandler;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                ...
                .and()
                .formLogin().successHandler(new MyAuthenticationSuccessHandler());
    }

    ...
}

--Specify an instance of ʻAuthenticationSuccessHandler created by the successHandler () `method

The state of operation is omitted because it only redirects to /hello.html.

When specified at the same time as the default target

If you specify the default target and ʻAuthenticationSuccessHandler` at the same time, it works as follows.

--In the case of namespace, ʻAuthenticationSuccessHandler` has priority --In the case of Java Configuration, it is better to set it later

The namespace is prioritized and the setting is decided, but Java Configuration overwrites the setting when the method is called, so this difference is made.

Implement the process when login fails

MyAuthenticationFailureHandler.java


package sample.spring.security.handler;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    
    @Override
    public void onAuthenticationFailure(
            HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
                throws IOException, ServletException {
        DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        redirectStrategy.sendRedirect(request, response, "/login");
    }
}

--Create a class that implements ʻAuthenticationFailureHandler` --Easy implementation just by skipping to the login page

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"
       ...>
    
    <bean id="authenticationFailureHandler"
          class="sample.spring.security.handler.MyAuthenticationFailureHandler" />
    
    <sec:http>
        ...
        <sec:form-login authentication-failure-handler-ref="authenticationFailureHandler"/>
        ...
    </sec:http>

    ...
</beans>

--Specify the bean of the created ʻAuthenticationFailureHandler with ʻauthentication-failure-handler-ref of<form-login>.

Java Configuration

MySpringSecurityConfig.java


...

import sample.spring.security.handler.MyAuthenticationFailureHandler;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                    .failureHandler(new MyAuthenticationFailureHandler());
    }

    ...
}

--Specify a handler with failureHandler ()

The state of operation is omitted because it simply returns to the login screen.

Identify the cause of login failure

ʻOnAuthenticationFailure () of ʻAuthenticationFailureHandler can receive the exception that occurred during authentication at the end of the argument. By examining this exception, the cause of the login failure can be identified.

If you want to adjust the error message when your account is locked, I feel like you can control it here.

exception situation
BadCredentialsException The user does not exist, the password is incorrect, etc.
LockedException UserDetails.isAccountNonLocked()ButfalseReturned
DisabledException UserDetails.isEnabled()ButfalseReturned
AccountExpiredException UserDetails.isAccountNonExpired()ButfalseReturned
CredentialsExpiredException UserDetails.isCredentialsNonExpired()ButfalseReturned
SessionAuthenticationException When the number of sessions exceeds the upper limit (Details at a later date

Authorization

Hello World

Implementation

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"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       ...>

    ...

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated() and hasAuthority('USER')" />
        <sec:form-login />
        <sec:logout />
    </sec:http>
    
    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user
                    name="hoge"
                    password="hoge"
                    authorities="USER" />
                <sec:user
                    name="fuga"
                    password="fuga"
                    authorities="" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

Java Configuration

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;

import java.util.Collections;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().access("isAuthenticated() and hasAuthority('USER')")
                .and()
                .formLogin();
    }
    
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("hoge")
            .password("hoge")
            .authorities("USER")
            .and()
            .withUser("fuga")
            .password("fuga")
            .authorities(Collections.emptyList());
    }
}

Operation check

spring-security.jpg

Log in as a hoge user

spring-security.jpg

The top page can be displayed.

spring-security.jpg

Then log in as the fuga user

spring-security.jpg

I get a 403 error.

Description

applicationContext.xml


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

--Define the conditions required for access with the ʻaccess attribute of . --SpEL (Spring Expression Language), which is Spring's own expression language, is used for this condition. --Here you can use functions (hasAuthority () etc.) and constants (permitAll) that Spring Security has extended independently. --ʻIsAuthenticated () will be true if authenticated,hasAuthority ('USER')will be true if the user has the authority ʻUSER (connect with ʻand Both need to be met).

applicationContext.xml


    <sec:user
        name="hoge"
        password="hoge"
        authorities="USER" />
    <sec:user
        name="fuga"
        password="fuga"
        authorities="" />

--ʻThe character string specified in the authoritiesattribute becomes the authority that the user has. --If you specify more than one, separate them with, (example: ʻUSER, ADMIN).

How it works

Timing when authorization process is executed

認可処理のタイミング.png

The URL-based authorization process is done at the end of the series of Filter processes. It is done by ʻAccessDecisionManager in the process of FilterSecurityInterceptor`.

Judgment by voting

The implementation of ʻAccessDecisionManager` provided by Spring Security as standard determines access availability by " voting ".

The implementation class of ʻAccessDecisionManager can have multiple voting classes called ʻAccessDecisionVoter. Then, each ʻAccessDecisionVoter is made to check (vote) whether or not the current user has access authority to the target (secure object). The voting result will be one of "Grant (ʻACCESS_GRANTED)", "Reject (ʻACCESS_DENIED)", and "Abstention (ʻACCESS_ABSTAIN)". The implementation class of ʻAccessDecisionManager aggregates the voting results of ʻAccessDecisionVoter and decides the final access.

認可処理の流れ.png

ʻAffirmativeBased, ConsensusBased, and ʻUnanimousBased are prepared in the implementation class of ʻAccessDecisionManager` depending on the aggregation method.

The default is ʻAffirmativeBased`.

How to determine access rights

ʻThe three default implementation classes of AccessDecisionManager do not directly determine the presence or absence of access rights. It is the voter class that implements the ʻAccessDecisionVoter interface that actually determines the presence or absence of access rights.

ʻThe AccessDecisionVoter` interface is defined as follows (only some).

AccessDecisionVoter


package org.springframework.security.access;

import java.util.Collection;

import org.springframework.security.core.Authentication;

public interface AccessDecisionVoter<S> {
	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;

	...

	int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);

}

There is a method to vote vote (). This method determines the presence or absence of access rights from the argument information received, and returns one of the constants (ʻACCESS_GRANTED, ʻACCESS_ABSTAIN, ʻACCESS_DENIED) also defined in ʻAccessDecisionVoter.

Each argument has the following meaning.

The implementation class of ʻAccessDecisionVoter gets the privileges granted to the user from ʻauthentication. Then, it is judged whether or not the user has access authority by comparing with the security setting passed in ʻattributes`.

Implementation of AccessDecisionVoter

If true is specified in the<http>tag's ʻuse-expressions attribute (default is true), WebExpressionVoter is used to implement ʻAccessDecisionVoter.

This class is an Expression, a Voter that evaluates an expression (SpEL) to determine access.

ʻAccessDecisionVoter also has RoleVoterandPreInvocationAuthorizationAdviceVoter` used in method security.

Granted Authority, which represents the authority of the user

The ʻAuthentication interface defines a method called getAuthorities () . This method returns a collection of GrantedAuthority`.

GrantedAuthority is also an interface, as the name implies " permissions granted to the user ". GrantedAuthority has only one method defined that returns String called getAuthority (). This method is designed to return "a string representation of the permissions represented by an instance of GrantedAuthority "[^ 2]. In short, it is a character string that represents such authority, such as ROLE_USER or ʻOP_REGISTER_ITEM`.

[^ 2]: By design, if the authority cannot be expressed by a simple string, null is returned. However, the standard implementation (such as SimpleGrantedAuthority) basically returns a character string, so the explanation of that case is omitted here.

In Hello World, the value set in the ʻauthorities attribute of the tag is GrantedAuthority`.

applicationContext.xml


<sec:user
    name="hoge"
    password="hoge"
    authorities="USER" />★ This

The authority information set here is initially stored in the class that implements ʻUserDetetails. Then, when the authentication process is performed by ʻAuthenticationProvider, it is acquired by thegetAuthorities ()method of ʻUserDetails and stored in the ʻAuthentication instance.

Mechanism summary

認可仕組みまとめ.png

--FilterSecurityInterceptor instructs ʻAccessDecisionManager (ʻAffirmativeBased) to determine the access right. --ʻAffirmativeBased delegates access right determination to ʻAccessDecisionVoter (WebExpressionVoter). --WebExpressionVoter gets a collection of GrantedAuthority from ʻAuthentication. --WebExpressionVoter executes the conditional expression (SpEL) from the argument information and the GrantedAuthorityinformation. --If the evaluation result of the expression istrue, the authorization is returned, and if it is false, the rejection is returned as the voting result. --ʻAffirmativeBased aggregates the voting results of Voter and determines whether or not you have access rights. --If it is determined that you do not have access rights, ʻAccessDeniedException` is thrown. --If no exception is thrown, it means that you have access right and continue to the subsequent processing.

Expression-based access control

If WebExpressionVoter is used to implement ʻAccessDecisionVoter, expression-based access control can be defined in the ʻaccess attribute of the<intercept-url>tag.

Spring Expression Language (SpEL) is used for the expression used here. Spring Security has been extended to allow you to use your own functions and variables to make it easier to define access control.

Write this expression so that it ultimately evaluates to a single boolean. Then, if the evaluation result of the expression is true, access is permitted, and if it is false, access is denied.

Available functions / variables </ span>

Functions / variables Check contents / values Example
hasRole(String) Has the specified role hasRole('USER')
hasAnyRole(String...) Has any one of the specified roles hasAnyRole('FOO', 'ROLE_BAR')
hasAuthority(String) Have the specified permissions hasAuthority('CREATE_TICKET')
hasAnyAuthority(String...) Have any one of the specified permissions hasAnyAuthority('CREATE_TICKET', 'MANAGE_TICKET')
isAnonymous() Being anonymously authenticated -
isRememberMe() Remember-Must be Me certified -
isAuthenticated() Must be authenticated -
isFullyAuthenticated() Must be fully authenticated -
permitAll alwaystrueIs evaluated as -
denyAll alwaysfalseIs evaluated as -

The relationship between ʻisAnonymous (), ʻisRememberMe (), ʻisAuthenticated (), ʻisFullyAuthenticated () is as follows.

Anonymous authentication Remember-Me certification Full authentication[^3]
isAnonymous() true false false
isRememberMe() false true false
isAuthenticated() false true true
isFullyAuthenticated() false false true

[^ 3]: Credentials (password, etc.) are checked successfully and it is guaranteed to be authenticated (in short, after logging in to Form normally)

Difference between hasRole () and hasAuthority ()

Among the functions that can be used with WebExpressionVoter arehasRole ()andhasAuthority ()as functions to check the existence of authority. Both do not change the point of checking for the existence of GrantedAuthority given to the user.

The difference is that hasRole () completes the ROLE_ prefix.

User authority → USER ROLE_USER
hasRole('USER') false true
hasRole('ROLE_USER') false true
hasAuthority('USER') true false
hasAuthority('ROLE_USER') false true

hasAuthority () verifies that the string representation of GrantedAuthority matches exactly.

On the other hand, hasRole () complements ROLE_ and compares it with GrantedAuthority if the string passed as an argument does not start with ROLE_. (HasRole ('USER') complements ROLE_ and compares as ROLE_USER)

What is ROLE?

What is the difference between Role and Authority? Nothing changes. The only difference is whether it starts with ROLE_, both of which are actually GrantedAuthority.

The reason why the one with the prefix ROLE_ is treated specially is that ** this is my guess **, but it is due to the fact that Voter called RoleVoter was used in the past. I feel like it.

WebExpressionVoter was added in ver 3.0, and before that, it seems that the combination of ** probably ** RoleVoter and ʻAuthenticatedVoter` was used.

If you are using a namespace, you can do so by setting ʻuse-expression in to false`.

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"
       ...>
    
    <sec:http use-expressions="false">
        <sec:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <sec:intercept-url pattern="/**" access="ROLE_ADMIN" />
        <sec:form-login />
        <sec:logout />
    </sec:http>
    
    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user
                    name="user"
                    password="user"
                    authorities="ROLE_USER" />
                <sec:user
                    name="admin"
                    password="admin"
                    authorities="ROLE_ADMIN" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

SpEL cannot be used for ʻaccess of . Instead, write the settings for ʻAuthenticatedVoter and RoleVoter.

ʻIS_AUTHENTICATED_ANONYMOUSLY is a setting for ʻAuthenticatedVoter to verify anonymous authentication (that is, it is OK even if you are not logged in).

Also, ROLE_ADMIN verifies whether it has the authority ROLE_ADMIN in the settings for RoleVoter.

Since pattern =" / login " has only the setting for ʻAuthenticatedVoter, verification by RoleVoter is unnecessary. However, ʻAccessDecisionManager cannot judge whether it is necessary or not [^ 4], so for the time being, each ʻAccessDecisionVoter is requested to vote (here, ʻAuthenticatedVoter and RoleVoter are. Vote).

[^ 4]: ʻAccessDecisionManager doesn't know (don't know) if the actual class behind the ʻAccessDecisionVoter interface is RoleVoter

Whether it is necessary or not is determined by the supports (ConfigAttribute) method of each Voter. The following is the supports () method of RoleVoter.

RoleVoter supports()Method


    public boolean supports(ConfigAttribute attribute) {
        if ((attribute.getAttribute() != null)
                && attribute.getAttribute().startsWith(getRolePrefix())) {
            return true;
        }
        else {
            return false;
        }
    }

For ʻattribute, the value specified by the ʻaccess attribute is passed (if multiple comma-separated values are specified, they are passed one by one). getRolePrefix () returns"ROLE_".

In other words, it verifies whether the value specified by the ʻaccess attribute starts with ROLE_, and if it starts, it supports it, otherwise it does not support it. If not supported, RoleVoter` will return a" abstention "as a result of voting.

In this way, by making the permission name start with ROLE_, RoleVoter will not perform unnecessary permission check processing. Similarly, ʻAuthenticatedVoter does not process unless the value specified by ʻaccess is a specific character string. In other words, setting values starting with ROLE_ are not supported and are not processed.

In this way, each Voter controls the presence or absence of support so that unnecessary judgment is not made. The result is a special prefix called ROLE_.

However, now that WebExpressionVoter has appeared, it is possible to define flexibly with SpEL, and I feel that the need to combine multiple Voters has diminished. (Actually, even if you try to combine WebExpressionVoter and RoleVoter, if you write ʻaccess =" ROLE_USER "`, an error will occur in the parsing of the SpEL expression, and you will not be able to combine it in the first place)

Refer to the bean from the SpEL expression

Implementation

MyExpression.java


package sample.spring.security.expression;

import org.springframework.security.core.Authentication;

public class MyExpression {

    public boolean check(Authentication authentication) {
        String name = authentication.getName();
        System.out.println("name = " + name);
        return "hoge".equals(name);
    }
}

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

    <bean id="myExpression" class="sample.spring.security.expression.MyExpression" />
    
    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="@myExpression.check(authentication)" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

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

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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;
import sample.spring.security.expression.MyExpression;

import java.util.Collections;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().access("@myExpression.check(authentication)")
                .and()
                .formLogin();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("hoge")
                .password("hoge")
                .authorities(Collections.emptyList())
                .and()
                .withUser("fuga")
                .password("fuga")
                .authorities(Collections.emptyList());
    }
    
    @Bean
    public MyExpression myExpression() {
        return new MyExpression();
    }
}

Operation check

When the left is accessed by hoge, the right is accessed by fuga

spring-security.jpg

Server log


name = hoge
name = fuga

Description

MyExpression.java


package sample.spring.security.expression;

import org.springframework.security.core.Authentication;

public class MyExpression {

    public boolean check(Authentication authentication) {
        String name = authentication.getName();
        System.out.println("name = " + name);
        return "hoge".equals(name);
    }
}

applicationContext.xml


    <sec:http>
        ...
        <sec:intercept-url pattern="/**" access="@myExpression.check(authentication)" />
        ...
    </sec:http>

--You can refer to any bean with @bean name

Objects for which constants and functions are defined

permitAll andhasAuthority ()are [SecurityExpressionRoot](http://docs.spring.io/autorepo/docs/spring-security/4.2.x-SNAPSHOT/apidocs/org/springframework/security/access/ It is defined in expression / SecurityExpressionRoot.html).

In addition, when access control to Filter is processed, [WebSecurityExpressionRoot](http://docs.spring.io/autorepo/docs/spring-security/4.2.x-SNAPSHOT/apidocs/org/springframework/security/ web / access / expression / WebSecurityExpressionRoot.html) is used.

Use your own Voter

Implementation

AcceptFugaVoter.java


package sample.spring.security.voter;

import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

public class AcceptFugaVoter implements AccessDecisionVoter<Object> {
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
        Object principal = authentication.getPrincipal();
        if (!(principal instanceof UserDetails)) {
            return ACCESS_ABSTAIN;
        }

        String username = ((UserDetails)principal).getUsername();
        return "fuga".equals(username) ? ACCESS_GRANTED : ACCESS_DENIED;
    }
}

A sloppy Voter that allows access if the username is fuga.

namespace

application.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>
    
    <bean id="accessDecisionManager"
          class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter" />
                <bean class="sample.spring.security.voter.AcceptFugaVoter" />
            </list>
        </constructor-arg>
    </bean>
    
    <sec:http access-decision-manager-ref="accessDecisionManager">
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="hasAuthority('HOGE')" />
        <sec:form-login />
        <sec:logout />
    </sec:http>
    
    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user
                    name="hoge"
                    password="hoge"
                    authorities="HOGE" />
                <sec:user
                    name="fuga"
                    password="fuga"
                    authorities="" />
                <sec:user
                    name="piyo"
                    password="piyo"
                    authorities="" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

Java Configuration

python


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.vote.AffirmativeBased;
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;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import sample.spring.security.voter.AcceptFugaVoter;

import java.util.Arrays;
import java.util.Collections;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .accessDecisionManager(this.createAccessDecisionManager())
                .antMatchers("/login").permitAll()
                .anyRequest().hasAuthority("HOGE")
                .and()
                .formLogin();
    }
    
    private AccessDecisionManager createAccessDecisionManager() {
        return new AffirmativeBased(Arrays.asList(
            new WebExpressionVoter(),
            new AcceptFugaVoter()
        ));
    }
    
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("hoge")
            .password("hoge")
            .authorities("HOGE")
        .and()
            .withUser("fuga")
            .password("fuga")
            .authorities(Collections.emptyList())
        .and()
            .withUser("piyo")
            .password("piyo")
            .authorities(Collections.emptyList());
    }
}

Operation check

From left to right, the state after logging in as the hoge, fuga, piyo user.

spring-security.jpg

Description

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"
       ...>
    
    <bean id="accessDecisionManager"
          class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter" />
                <bean class="sample.spring.security.voter.AcceptFugaVoter" />
            </list>
        </constructor-arg>
    </bean>
    
    <sec:http access-decision-manager-ref="accessDecisionManager">
        ...
    </sec:http>
    
    ...
</beans>

--You can specify ʻAccessDecisionManager with the tag ʻaccess-decision-manager-ref. --In case of Java Configuration, it can be specified by ʻaccessDecisionManager () method. --Pass ʻAccessDecisionManager with your ownVoter set here. --Specify Voter in the constructor of ʻAffirmativeBased. --Same for ConsensusBased and ʻUnanimousBased.

Role inheritance

Role has an inheritance relationship

Role usually has an inheritance relationship.

For example, if there are roles for "user" and "administrator", "administrator" usually inherits "user". In other words, what a "user" can do can also be done by an "administrator".

However, if you try to control this simply with or without Role, the settings will be as follows.

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"
       ...>
    
    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/hierarchy/admin" access="hasRole('ADMIN')" />
        <sec:intercept-url pattern="/hierarchy/user" access="hasRole('USER') or hasRole('ADMIN')" />
        <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="user"
                    password="user"
                    authorities="ROLE_USER" />
                <sec:user
                    name="admin"
                    password="admin"
                    authorities="ROLE_ADMIN" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

/ hierarchy / admin is accessible only to the" administrator ", and / hierarchy / user is accessible to the "user".

The / hierarchy / admin is simplyhasRole ('ADMIN'). However, / hierarchy / user ishasRole ('USER') or hasRole ('ADMIN'). If only hasRole ('USER') is used, it will be blocked when accessed by a user who has only ROLE_ADMIN, so this is a redundant setting.

In this way, if you try to realize the inheritance relationship of Role simply with or without Role, set the child Role (ROLE_ADMIN) in all the places where the parent Role (ROLE_USER) is specified. You will have to.

There is also a way to set ʻaccess only to hasRole ('USER') , and to set ʻADMIN to ʻauthorities, set ʻUSER as well. However, in any case, there is no difference in that the description is redundant.

Define Role inheritance using RoleHierarchy

Spring Security provides a mechanism to define the inheritance relationship of Role.

The previous setting can be rewritten as follows by using RoleHierarchy.

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"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       ...>

    <bean id="roleHierarchy"
          class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
        <property name="hierarchy">
            <value>
                ROLE_ADMIN > ROLE_USER
            </value>
        </property>
    </bean>
    
    <bean id="expressionHandler"
          class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
        <property name="roleHierarchy" ref="roleHierarchy" />
    </bean>
    
    <sec:http>
        <sec:expression-handler ref="expressionHandler" />
        
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/hierarchy/admin" access="hasRole('ADMIN')" />
        <sec:intercept-url pattern="/hierarchy/user" access="hasRole('USER')" />
        <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="user"
                    password="user"
                    authorities="ROLE_USER" />
                <sec:user
                    name="admin"
                    password="admin"
                    authorities="ROLE_ADMIN" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
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;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .expressionHandler(this.createSecurityExpressionHandler())
                .antMatchers("/login").permitAll()
                .antMatchers("/hierarchy/user").hasRole("USER")
                .antMatchers("/hierarchy/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin();
    }
    
    private SecurityExpressionHandler<FilterInvocation> createSecurityExpressionHandler() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");

        DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy);
        
        return expressionHandler;
    }
    
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user")
            .password("user")
            .roles("USER")
        .and()
            .withUser("admin")
            .password("admin")
            .roles("ADMIN");
    }
}

Operation check

Log in as ʻuser and ʻadmin to access / hierarchy / user and / hierarchy / admin.

** When logged in as user **

spring-security.jpg

** If you log in as admin **

spring-security.jpg

Description

Definition of RoleHierarchyImpl


    <bean id="roleHierarchy"
          class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
        <property name="hierarchy">
            <value>
                ROLE_ADMIN > ROLE_USER
            </value>
        </property>
    </bean>

--Lean RoleHierarchyImpl as an implementation class of the RoleHierarchy interface. --At this time, describe the definition of the inheritance relationship of Role in the hierarchy property. --The definition of inheritance relationship is described by child role name> parent role name.

When defining more than one,

ROLE_A > ROLE_B
ROLE_B > ROLE_C

You can do it like this (there is no need for line breaks, but it is easier to see).

Set RoleHierarchyImpl to be used in expression evaluation


    <bean id="roleHierarchy"
          class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
        ...
    </bean>
    
    <bean id="expressionHandler"
          class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
        <property name="roleHierarchy" ref="roleHierarchy" />
    </bean>
    
    <sec:http>
        <sec:expression-handler ref="expressionHandler" />
        
        ...
    </sec:http>

Then set this RoleHierarchyImpl to be used when the SpEL is evaluated.

To do this, register SecurityExpressionHandler as a bean. Use DefaultWebSecurityExpressionHandler as the implementation class and specify the RoleHierarchyImpl defined earlier in the roleHierarchy property.

Then add <expression-handler> as a sub-element of <http> and specify SecurityExpressionHandler in the ref attribute.

Do not use WebExpressionVoter

Use RoleHierarchyVoter without using WebExpressionVoter.

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

    <bean id="roleHierarchy"
          class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
        <property name="hierarchy">
            <value>
                ROLE_ADMIN > ROLE_USER
            </value>
        </property>
    </bean>
    
    <bean id="roleHierarchyVoter"
          class="org.springframework.security.access.vote.RoleHierarchyVoter">
        <constructor-arg ref="roleHierarchy" />
    </bean>
    
    <bean id="accessDecisionManager"
          class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
                <ref bean="roleHierarchyVoter" />
            </list>
        </constructor-arg>
    </bean>
    
    <sec:http use-expressions="false"
              access-decision-manager-ref="accessDecisionManager">
        <sec:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <sec:intercept-url pattern="/hierarchy/admin" access="ROLE_ADMIN" />
        <sec:intercept-url pattern="/hierarchy/user" access="ROLE_USER" />
        <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
        <sec:form-login />
        <sec:logout />
    </sec:http>
    
    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user
                    name="user"
                    password="user"
                    authorities="ROLE_USER" />
                <sec:user
                    name="admin"
                    password="admin"
                    authorities="ROLE_ADMIN" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

Control at the time of authorization error

Specify the error page

Implementation

access-denied.html


<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Authorization error</title>
    </head>
    <body>
        <h1>That operation is not allowed!</h1>
    </body>
</html>

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="/test" access="hasAuthority('HOGE')" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
        <sec:access-denied-handler error-page="/access-denied.html" />
    </sec:http>

    ...
</beans>

Java Configuration

MySpringSecurityConfig.java


...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                ...
                .and()
                .exceptionHandling().accessDeniedPage("/access-denied.html");
    }

    ...
}

Operation check

After logging in with hoge, access / test

spring-security.jpg

Description

applicationContext.xml


<sec:access-denied-handler error-page="/access-denied.html" />

MySpringSecurityConfig.java


.exceptionHandling().accessDeniedPage("/access-denied.html");

--For namespace, add the <access-denied-handler> tag and specify it with the ʻerror-pageattribute. --For Java Configuration, specify with ʻexceptionHandler (). accessDeniedPage (String) --Transition is done forward

Control with arbitrary implementation

Implementation

MyAccessDeniedHandler.java


package sample.spring.security.handler;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAccessDeniedHandler implements AccessDeniedHandler {
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        if (response.isCommitted()) {
            return;
        }
        
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);

        DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        redirectStrategy.sendRedirect(request, response, "/access-denied.html");
    }
}

--ʻCreate a class that implements AccessDeniedHandlerand implement the processing at the time of authorization error in thehandle ()method. --Here, it is implemented to redirect to/access-denied.html`

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"
       ...>
    
    <bean id="accessDeniedHandler"
          class="sample.spring.security.handler.MyAccessDeniedHandler" />
    
    <sec:http>
        ...
        <sec:access-denied-handler ref="accessDeniedHandler" />
    </sec:http>

    ...
</beans>

--Specify the bean of ʻAccessDeniedHandler in the refattribute of`

Java Configuration

MySpringSecurityConfig.java


...

import sample.spring.security.handler.MyAccessDeniedHandler;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                ...
                .and()
                .exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
    }

    ...
}

--Pass an instance of ʻAccessDeniedHandler with ʻaccessDeniedHandler ().

Execution result

Operate in the same way as when ʻerror-page` was specified.

spring-security.jpg

Now you can see that the URL has changed to be a redirect.

reference

Recommended Posts

Spring Security usage memo Authentication / authorization
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 response header
Spring Security usage memo session management
Spring Security usage memo Basic / mechanism
Authentication / authorization with Spring Security & Thymeleaf
Spring Security Usage Memo Domain Object Security (ACL)
About Spring Security authentication
Spring Shell usage memo
Spring Security usage memo: Cooperation with Spring MVC and Boot
Implemented authentication function with Spring Security ②
Implemented authentication function with Spring Security ③
Spring Boot Tutorial Using Spring Security Authentication
Implemented authentication function with Spring Security ①
Learn Spring Security authentication processing architecture
Set Spring Security authentication result to JSON
DB authentication with Spring Security & hashing with BCrypt
Achieve BASIC authentication with Spring Boot + Spring Security
Spring retrospective memo
JavaParser usage memo
WatchService usage memo
PlantUML usage memo
JUnit5 usage memo
Try LDAP authentication with Spring Security (Spring Boot) + OpenLDAP
Add your own authentication items with Spring Security
[Introduction to Spring Boot] Authentication function with Spring Security
JJUG CCC Spring 2018 memo
Spring boot memo writing (1)
Spring Security causes 403 forbidden
Spring boot memo writing (2)
Create API key authentication for Web API in Spring Security
A new employee tried to create an authentication / authorization function from scratch with Spring Security
[Personal memo] About Spring framework
JJUG CCC 2018 Spring participation memo
Spring Framework self-study memo series_1
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
I get a 404 error when testing forms authentication with Spring Security