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
ʻUserDetailsService` is responsible for retrieving user information.
Spring Security provides several classes that implement ʻUserDetailsService`.
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`.
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.
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`.
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.
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`.
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);
}
}
BCryptPasswordEncoder
is registered as a bean)PasswordEncoder
is explicitly obtained from the container for simple implementation to check the operation, but in reality, the reference is obtained by DI the PasswordEncoder
to the container management bean.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.
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 the
SecurityContextassociated with the current request. --You can get the privileges granted to the current logged-in user (collection of
GrantedAuthority) 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
).
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.
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.
If you enable Form login but do not specify a login page, the default is a simple login page generated by Spring Security.
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.
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");
}
...
}
If you try to access any page, you will be taken to your own login page.
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 be
password. --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)`
By default, the transition destination after login is controlled as follows.
/
) (default target)hello.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello</title>
</head>
<body>
<h1>Hello!!</h1>
</body>
</html>
Add a simple screen.
Access /hello.html
.
You will be taken to the login screen, so log in.
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.
log in.
It is skipped to the application root (/
).
You can change the transition destination after opening the login page directly and logging in with default-target-url
.
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)
Access the login screen (/ login
) directly.
log in
You will be skipped to the URL (/hello.html
) specified by default-target-url
.
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.
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)
Access the appropriate URL.
You will be taken to the login screen, so log in.
You will be skipped to the page specified by default-target-url
.
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
.
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.
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.
ʻ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() Butfalse Returned |
DisabledException |
UserDetails.isEnabled() Butfalse Returned |
AccountExpiredException |
UserDetails.isAccountNonExpired() Butfalse Returned |
CredentialsExpiredException |
UserDetails.isCredentialsNonExpired() Butfalse Returned |
SessionAuthenticationException |
When the number of sessions exceeds the upper limit (Details at a later date) |
Hello World
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());
}
}
Log in as a hoge user
The top page can be displayed.
Then log in as the fuga user
I get a 403 error.
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
).
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`.
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.
ʻAffirmativeBased,
ConsensusBased, and ʻUnanimousBased
are prepared in the implementation class of ʻAccessDecisionManager` depending on the aggregation method.
AffirmativeBased
--If there is even one vote for "grant", the access right is granted.
--If all votes are "abstain", access is not granted by default.ConsensusBased
--If there are more "grant" votes than "deny" votes, the access right is granted.
--If the number of "grant" and "deny" is the same, the access right is ** granted ** by default.
--If all votes are "abstain", access is not granted by default.UnanimousBased
--If all votes are "granted", access is granted.
--If all votes are "abstain", access is not granted by default.The default is ʻAffirmativeBased`.
ʻ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.
authentication
--Authentication information of the currently accessing userobject
--Security object (secure object)
--In the case of Filter
, FilterInvocation
--For method security, MethodInvocation
attributes
--Security settings related to secure objects
--In the Hello World example, the expression (ʻisAuthenticated () and hasAuthority ('USER')) set in the ʻaccess
attribute of the<intercept-url>
tag.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`.
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
RoleVoterand
PreInvocationAuthorizationAdviceVoter` used in method security.
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.
--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 is
true, 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.
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.
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 |
alwaystrue Is evaluated as |
- |
denyAll |
alwaysfalse Is 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)
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 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 Voter
s 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)
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();
}
}
When the left is accessed by hoge
, the right is accessed by fuga
Server log
name = hoge
name = fuga
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
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.
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());
}
}
From left to right, the state after logging in as the hoge
, fuga
, piyo
user.
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 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.
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");
}
}
Log in as ʻuser and ʻadmin
to access / hierarchy / user
and / hierarchy / admin
.
** When logged in as user **
** If you log in as admin **
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.
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>
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");
}
...
}
After logging in with hoge
, access / test
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
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 the
handle ()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 ()
.
Operate in the same way as when ʻerror-page` was specified.
Now you can see that the URL has changed to be a redirect.
Recommended Posts