Basic and mechanical story Authentication / Authorization Story Remember-Me story CSRF 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
By default, the same user can create multiple sessions. In other words, the same user can log in as many times as they want from different terminals.
By changing the settings, you can limit the number of simultaneous sessions or prevent multiple logins at all.
namespace
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
</web-app>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login />
<sec:logout />
<sec:session-management>
<sec:concurrency-control max-sessions="1" />
</sec:session-management>
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="hoge" password="hoge" authorities="" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Java Configuration
MySpringSecurityInitializer.java
package sample.spring.security;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class MySpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public MySpringSecurityInitializer() {
super(MySpringSecurityConfig.class);
}
@Override
protected boolean enableHttpSessionEventPublisher() {
return true;
}
}
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().authenticated()
.and()
.formLogin()
.and()
.sessionManagement().maximumSessions(1);
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("hoge")
.password("hoge")
.authorities(Collections.emptyList());
}
}
Log in with your first browser.
Log in as the same user in another browser.
When I redisplay the first browser, an error message is displayed.
web.xml
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
MySpringSecurityInitializer.java
@Override
protected boolean enableHttpSessionEventPublisher() {
return true;
}
--You need to register HttpSessionEventPublisher
as a listener so that Spring Security can detect that a new session has been created.
--If you are using web.xml
, declare it normally with<listener>
.
--If you are using WebApplicationInitializer
, you can override the ʻenableHttpSessionEventPublisher ()method to return
true and it will register
HttpSessionEventPublisher` as a listener.
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:session-management>
<sec:concurrency-control max-sessions="1" />
</sec:session-management>
</sec:http>
...
</beans>
--Add the <session-management>
tag, add the <concurrency-control>
tag, and specify the maximum number of sessions with the max-sessions
attribute.
MySpringSecurityConfig.java
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.sessionManagement().maximumSessions(1);
}
...
}
--Specify with sessionManagement (). MaximumSessions (int)
.
By default, the screen will display only an error message, so skip to the specified page (for example, 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"
...>
<sec:http>
...
<sec:session-management >
<sec:concurrency-control max-sessions="1" expired-url="/login" />
</sec:session-management>
</sec:http>
...
</beans>
--Specify the URL and path in the ʻexpired-url` attribute.
Java Configuration
MySpringSecurityConfig.java
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.sessionManagement().maximumSessions(1).expiredUrl("/login");
}
...
}
--Specified by ʻexpiredUrl (String) `method.
MySessionInformationExpiredStrategy.java
package sample.spring.security.session;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import javax.servlet.ServletException;
import java.io.IOException;
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(event.getRequest(), event.getResponse(), "/login");
}
}
--Create a class that implements SessionInformationExpiredStrategy
.
--ʻOnExpiredSessionDetected ()method implements error processing. --Here, we are redirecting to
/ login`.
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="sies" class="sample.spring.security.session.MySessionInformationExpiredStrategy" />
<sec:http>
...
<sec:session-management >
<sec:concurrency-control max-sessions="1" expired-session-strategy-ref="sies" />
</sec:session-management>
</sec:http>
...
</beans>
--Specify the bean of SessionInformationExpiredStrategy
with ʻexpired-session-strategy-ref`.
Java Configuration
MySpringSecurityConfig.java
...
import sample.spring.security.session.MySessionInformationExpiredStrategy;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.sessionManagement()
.maximumSessions(1)
.expiredSessionStrategy(new MySessionInformationExpiredStrategy());
}
...
}
―― Register with the ʻexpiredSessionStrategy (SessionInformationExpiredStrategy) `method.
If you just specify max-sessions
(maximumSessions
), old sessions will be discarded if you log in beyond the upper limit.
Conversely, if you try to log in beyond the limit, you can make an error if you try to create a new session. However, be aware that if you enable this setting, you may not be able to log in again until your old session expires or you explicitly log out.
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:session-management >
<sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</sec:session-management>
</sec:http>
...
</beans>
--Specify true
for ʻerror-if-maximum-exceeded`.
Java Configuration
MySpringSecurityConfig.java
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true);
}
...
}
--Pass true
in themaxSessionsPreventsLogin (boolean)
method.
** Operation check **
Log in with the first browser.
If you try to log in as the same user on the second browser, an error will occur.
max-session-error.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Max Session</title>
</head>
<body>
<h1>The number of sessions has reached the limit</h1>
<%@page import="org.springframework.security.core.AuthenticationException" %>
<%@page import="org.springframework.security.web.WebAttributes" %>
<%
AuthenticationException e = (AuthenticationException)session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
pageContext.setAttribute("errorMessage", e.getMessage());
%>
<h2 style="color: red;">${errorMessage}</h2>
</body>
</html>
The page to be the transition destination. An error message is being displayed.
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="/max-session-error.jsp" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login authentication-failure-url="/max-session-error.jsp" />
<sec:logout />
<sec:session-management>
<sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</sec:session-management>
</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("/max-session-error.jsp").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.failureUrl("/max-session-error.jsp")
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true);
}
...
}
Log in with the first browser.
When you log in with the second browser, you will be taken to the specified error page (max-session-error.jsp
).
applicationContext.xml
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/max-session-error.jsp" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login authentication-failure-url="/max-session-error.jsp" />
--If the number of sessions exceeds the upper limit and you try to log in to Form, the error page will be done by the ʻauthentication-failure-urlattribute of the
tag which is the setting of Form login. ――Of course, the error page that is normally skipped by mistaken password etc. will be the page specified here. ――So, in reality, it seems necessary to specify the URL of the login screen, identify the error on the login screen side (or the controller of the login screen), and switch the message. --Alternatively, you can choose to handle authentication errors by specifying ʻAuthenticationFailureHandler
in ʻauthentication-failure-handler-ref (for ʻAuthenticationFailureHandler
](http://qiita.com/opengl-" 8080 / items / 032ed0fa27a239bdc1cc #% E3% 83% AD% E3% 82% B0% E3% 82% A4% E3% 83% B3% E3% 81% AB% E5% A4% B1% E6% 95% 97% E3 % 81% 97% E3% 81% 9F% E3% 81% A8% E3% 81% 8D% E3% 81% AE% E5% 87% A6% E7% 90% 86% E3% 82% 92% E5% AE % 9F% E8% A3% 85% E3% 81% 99% E3% 82% 8B))
--In case of session limit error, SessionAuthenticationException
is thrown.
Why is the specification of the transition destination when an error occurs in the session upper limit check is the setting of the <form-login>
tag?
applicationContext.xml
<sec:session-management>
<sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</sec:session-management>
--When you add the <session-management>
tag, a Filter called SessionManagementFilter
is added.
--The number of simultaneous sessions is checked by the class SessionAuthenticationStrategy
that this Filter has.
--Specifically, ConcurrentSessionControlAuthenticationStrategy
.
--However, this SessionAuthenticationStrategy
also has a ʻUsernamePasswordAuthenticationFilter that executes the Form login process. --Strictly speaking, it is possessed by the parent class ʻAbstractAuthenticationProcessingFilter
.
--ʻIf authentication with UsernamePasswordAuthenticationFilteris successful, the authentication process related to the session is delegated to
SessionAuthenticationStrategy. --And at that time, the number of simultaneous sessions is checked. --If an error exceeds the upper limit, it is natural to handle the error with ʻUsernamePasswordAuthenticationFilter
.
--SessionManagementFilter
is waiting after ʻUsernamePasswordAuthenticationFilter`.
--Therefore, the Form login setting will be adopted for the error page specification that exceeds the maximum number of sessions.
--SessionManagementFilter
is in charge of processing when logged in by non-interactive authentication processing.
--A typical non-interactive authentication process is automatic login with Remember-Me authentication.
This is a personal guess and I don't know if it's correct </ font>
--If the Form login is successful, the Filter chain will be interrupted and you will be redirected to the page after login.
--That is, Filters after ʻUsernamePasswordAuthenticationFilterare not executed. --Since
SessionManagementFilter is a Filter after ʻUsernamePasswordAuthenticationFilter
, it is not possible to entrust the session check at the time of Form login to SessionManagementFilter
.
--What if you put SessionManagementFilter
before ʻUsernamePasswordAuthenticationFilter? I thought, but it may be because it is overwhelming to put
SessionManagementFilter, which should be checked after successful login, before ʻUsernamePasswordAuthenticationFilter
where login processing is performed.
Implementation
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login />
<sec:remember-me />
<sec:logout />
<sec:session-management>
<sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</sec:session-management>
</sec:http>
...
</beans>
** Operation result **
Log in with Remember-Me enabled (browser A).
Leave it for a while and wait for the session to time out (assuming the Remember-Me token has not expired).
Log in with another browser (browser B) and redraw the screen with the time-out browser (browser A).
You are skipped to the 401 error screen (browser A).
By default, if Remember-Me fails to exceed the session limit, it will result in a 401 error as described above and will be skipped to the server error page.
To skip to any path, the reference says that you should specify the session-authentication-error-url
attribute of the<session-management>
tag.
If the second authentication takes place through another non-interactive mechanism, such as "remember-me", an "unauthorized" (401) error will be sent to the client. If instead you want to use an error page, you can add the attribute session-authentication-error-url to the session-management element. (Translation) If the second authentication is done by another non-interactive mechanism (eg Remember-Me) (other than Form login), a 401 unauthorized error will be sent to the client. If you want to use the error page instead, you can add the
session-authentication-error-url
attribute to thesession-management
element.
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#ns-concurrent-sessions
However, if you actually specify that path, a redirect loop will occur.
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>
<div>
<label>Remember-Me : <input type="checkbox" name="remember-me"></label>
</div>
<input type="submit" value="login" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</body>
</html>
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:remember-me />
<sec:logout />
<sec:session-management session-authentication-error-url="/my-login.jsp">
<sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</sec:session-management>
</sec:http>
...
</beans>
** Operation result **
In case of Remember-Me, the number of sessions is checked by SessionManagementFilter
.
Here, the redirect is performed to the URL specified by session-authentication-error-url
.
Then, when redirected to the URL, a series of Filters will be executed again, and Remember-Me will automatically log in and SessionManagementFilter
will check the number of sessions.
Naturally, the error occurs again and the redirect is made to the URL specified by session-authentication-error-url
.
As a result, a redirect loop occurs.
I searched, but I couldn't find any trouble with the same story. I can't find a workaround.
Am I misunderstanding something ...?
By the way, this phenomenon does not occur when using the login page generated by Spring Security by default.
This is avoided because the DefaultLoginPageGeneratingFilter
exists before the SessionManagementFilter
, so the default login page is transitioned before the session count check is repeated.
For the explanation of the session fixation attack itself, refer to IPA explanation page.
As a countermeasure, it is recommended to change the session ID before and after login.
Spring Security also has this countermeasure enabled by default, and the session ID (JSESSIONID
) is changed before and after login.
JSESSIONID
before login is376D515 ...
log in
You can see that the JSESSIONID
after login has changed to30194EA ...
.
For the time being, whether or not to change the session ID after this login can be changed in the settings.
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:session-management session-fixation-protection="none" />
</sec:http>
...
</beans>
--Specify the session-fixation-protection
attribute of<session-management>
--Here, none
is specified to disable the session ID change.
Java Configuration
MySpringSecurityConfig.java
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.sessionManagement()
.sessionFixation().none();
}
...
}
--Set as sessionFixation (). none ()
Before login 8CC8D2 ...
Even after logging in 8CC8D2 ...
It hasn't changed.
However, I can't imagine a situation where you have to specify none
. .. ..
You can specify the following values for session-fixation-protection
:
value | Description |
---|---|
none |
Do not change session ID |
newSession |
Create a new session. Information other than the attribute information managed by Spring Security is discarded. |
migrateSession |
Create a new session and port attributes as well (Servlet 3).Default in 0 or less environment) |
changeSessionId |
HttpServletRequest Added tochangeSessionId() Reassign the ID with (Servlet 3).Default in 1 and above environments) |
Recommended Posts