[JAVA] Gestion des sessions de mémo d'utilisation de Spring Security

Histoire de base et systématique Histoire de certification / autorisation Histoire Remember-Me Histoire CSRF L'histoire de l'en-tête de la réponse Histoire de la sécurité de la méthode Histoire CORS L'histoire de Run-As Histoire ACL Test story Parlez de la coopération avec MVC et Boot

Edition supplémentaire Ce que Spring Security peut et ne peut pas faire

Contrôle du nombre de sessions simultanées

Par défaut, le même utilisateur peut créer plusieurs sessions. En d'autres termes, un même utilisateur peut se connecter autant de fois qu'il le souhaite à partir de différents terminaux.

En modifiant les paramètres, vous pouvez limiter le nombre de sessions simultanées ou empêcher les connexions multiples du tout.

Limiter le nombre de sessions simultanées

la mise en oeuvre

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

Contrôle de fonctionnement

spring-security.jpg

Connectez-vous avec le premier navigateur.

spring-security.jpg

Connectez-vous avec le même utilisateur dans un autre navigateur.

spring-security.jpg

Lorsque je réaffiche le premier navigateur, un message d'erreur s'affiche.

La description

web.xml


    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>

MySpringSecurityInitializer.java


    @Override
    protected boolean enableHttpSessionEventPublisher() {
        return true;
    }

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>

--Ajoutez la balise <session-management>, ajoutez la balise <concurrency-control> et spécifiez le nombre maximum de sessions avec l'attribut max-sessions.

MySpringSecurityConfig.java


...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                ...
                .and()
                .sessionManagement().maximumSessions(1);
    }
    
    ...
}

--Spécifiez avec sessionManagement (). MaximumSessions (int).

Spécifiez l'écran d'erreur

Par défaut, l'écran n'affichera qu'un message d'erreur, alors passez à la page spécifiée (par exemple, la page de connexion).

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>

--Spécifiez l'URL et le chemin dans l'attribut ʻexpired-url`.

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

--Spécifié par la méthode ʻexpiredUrl (String) `.

Précisez le traitement au moment de l'erreur dans l'implémentation

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

--Créez une classe qui implémente SessionInformationExpiredStrategy. La méthode --ʻOnExpiredSessionDetected () `implémente le traitement des erreurs.

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>

--Spécifiez le bean de SessionInformationExpiredStrategy avec ʻ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());
    }
    
    ...
}

―― Enregistrez-vous avec la méthode ʻexpiredSessionStrategy (SessionInformationExpiredStrategy) `.

Vous empêcher de vous connecter au-delà de la limite

Si vous spécifiez simplement max-sessions ( maximumSessions), si vous vous connectez au-delà de la limite supérieure, la session la plus ancienne sera supprimée en premier.

Inversement, si vous essayez de vous connecter au-delà de la limite, vous pouvez faire une erreur si vous essayez de créer une nouvelle session. Cependant, sachez que si vous activez ce paramètre, vous ne pourrez peut-être pas vous reconnecter jusqu'à ce que votre ancienne session expire ou que vous vous déconnectiez explicitement.

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>

--Spécifiez «true» pour «erreur-si-maximum-dépassé».

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

** Contrôle de fonctionnement **

spring-security.jpg

Connectez-vous avec le premier navigateur.

spring-security.jpg

Si vous essayez de vous connecter avec le même utilisateur sur le deuxième navigateur, une erreur se produit.

Spécifiez la destination de la transition au moment de l'erreur

la mise en oeuvre

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>Le nombre de sessions a atteint la limite</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>

La page qui sera la destination de la transition. Un message d'erreur s'affiche.

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

Contrôle de fonctionnement

spring-security.jpg

Connectez-vous avec le premier navigateur.

spring-security.jpg

Lorsque vous vous connectez avec le deuxième navigateur, vous serez redirigé vers la page d'erreur spécifiée (max-session-error.jsp).

La description

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

Pourquoi la connexion par formulaire est-elle définie?

Vérifié avec UsernamePasswordAuthenticationFilter

Pourquoi la spécification de la destination de transition lorsqu'une erreur se produit dans le contrôle du nombre maximum de sessions est-elle définie sur la balise <form-login>?

applicationContext.xml


        <sec:session-management>
            <sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
        </sec:session-management>

--Lorsque vous ajoutez la balise <session-management>, un filtre appelé SessionManagementFilter est ajouté.

Alors, quand SessionManagementFilter fonctionne-t-il?

--SessionManagementFilter est en charge du traitement lorsqu'il est connecté par un traitement d'authentification non interactif.

Est-il possible de confier le traitement à SessionManagementFilter même lors de la connexion à Form?

Ceci est une estimation personnelle et je ne sais pas si c'est correct </ font>

  • Si la connexion par formulaire réussit, la chaîne de filtrage sera interrompue et vous serez redirigé vers la page après la connexion.
  • Autrement dit, les filtres après ʻUsernamePasswordAuthenticationFilter` ne sont pas exécutés.
  • Puisque SessionManagementFilter est un filtre après ʻUsernamePasswordAuthenticationFilter, il n'est pas possible de confier le contrôle de session au moment de la connexion par formulaire à SessionManagementFilter`.
  • Et si vous mettez SessionManagementFilter avant ʻUsernamePasswordAuthenticationFilter? J'ai pensé, mais c'est peut-être parce qu'il est accablant de mettre SessionManagementFilter, qui devrait être vérifié après une connexion réussie, avant ʻUsernamePasswordAuthenticationFilter où le processus de connexion est effectué.

Tu ne peux pas le combiner avec Remember-Me?

Lorsque le nombre de sessions est dépassé par la connexion automatique de Remember-Me

La mise en oeuvre

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>

** Résultat de l'opération **

spring-security.jpg

Connectez-vous avec Remember-Me activé (navigateur A).

Laissez-le pendant un moment et attendez que la session expire (en supposant que le jeton Remember-Me n'a pas expiré).

Connectez-vous avec un autre navigateur (navigateur B) et redessinez l'écran avec le navigateur timeout (navigateur A).

spring-security.jpg

Vous êtes passé à l'écran d'erreur 401 (navigateur A).

Lorsque le chemin au moment de l'erreur est spécifié

Par défaut, si Remember-Me ne dépasse pas la limite de session, il en résultera une erreur 401 comme décrit ci-dessus et sera ignoré à la page d'erreur du serveur.

Pour passer à n'importe quel chemin, la référence indique que vous devez spécifier l'attribut session-authentication-error-url de la balise <session-management>.

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. (Traduction) Si la deuxième authentification est effectuée par un autre mécanisme non interactif (par exemple Remember-Me) (autre que la connexion par formulaire), une erreur 401 non autorisée sera envoyée au client. Si vous souhaitez utiliser la page d'erreur à la place, vous pouvez ajouter l'attribut session-authentication-error-url à l'élément session-management.

https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#ns-concurrent-sessions

Cependant, si vous spécifiez réellement ce chemin, une boucle de redirection se produira.

La mise en oeuvre

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>Nom d'utilisateur: <input type="text" name="username" /></label>
            </div>
            <div>
                <label>mot de passe: <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>

** Résultat de l'opération **

spring-security.jpg

Pourquoi cela arrive-t-il?

Dans le cas de Remember-Me, le nombre de sessions est vérifié avec SessionManagementFilter. Ici, la redirection est effectuée vers l'URL spécifiée par session-authentication-error-url.

Ensuite, une fois redirigé vers l'URL, une série de filtres sera à nouveau exécutée, et Remember-Me se connectera automatiquement et SessionManagementFilter vérifiera le nombre de sessions. Naturellement, l'erreur se reproduit et la redirection est effectuée vers l'URL spécifiée par session-authentication-error-url.

En conséquence, une boucle de redirection se produit.

J'ai cherché, mais je n'ai trouvé aucun problème avec la même histoire. Je ne trouve pas de solution de contournement.

Suis-je mal compris quelque chose ...?

En passant, ce problème ne se produit pas si vous utilisez la page de connexion que Spring Security génère par défaut. Ceci est évité car le DefaultLoginPageGeneratingFilter existe avant le SessionManagementFilter, donc la page de connexion par défaut est transférée avant que le contrôle du nombre de sessions ne soit répété.

Contre-mesures d'attaque fixe de session

Pour une description de l'attaque de fixation de session elle-même, voir page d'explication IPA.

En guise de contre-mesure, il est recommandé de modifier l'ID de session avant et après la connexion. Spring Security a également cette contre-mesure activée par défaut, et l'ID de session (JSESSIONID) est modifié avant et après la connexion.

Contrôle de fonctionnement

spring-security.jpg

JSESSIONID avant la connexion est 376D515 ...

s'identifier

spring-security.jpg

Vous pouvez voir que le JSESSIONID après la connexion est devenu 30194EA ....

Contrôler le changement d'identifiant de session

Pour le moment, la modification ou non de l'ID de session après cette connexion peut être modifiée dans les paramètres.

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>

--Spécifiez l'attribut session-fixation-protection de <session-management>

  • Ici, «aucun» est spécifié pour désactiver le changement d'ID de session.

Java Configuration

MySpringSecurityConfig.java


...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                ...
                .and()
                .sessionManagement()
                    .sessionFixation().none();
    }

    ...
}
  • Définir comme sessionFixation (). None ()

Contrôle de fonctionnement

spring-security.jpg

Avant de vous connecter 8CC8D2 ...

spring-security.jpg

Même après la connexion à 8CC8D2 ...

Cela n'a pas changé. Cependant, je ne peux pas imaginer une situation où vous devez spécifier «aucun». .. ..

Vous pouvez spécifier les valeurs suivantes pour session-fixation-protection:

valeur La description
none Ne changez pas l'ID de session
newSession Créez une nouvelle session. Tous les attributs autres que ceux gérés par Spring Security sont supprimés.
migrateSession Créez également une nouvelle session et des attributs de port (Servlet 3).Valeur par défaut dans les environnements inférieurs à 0)
changeSessionId HttpServletRequestAjouté àchangeSessionId()Réaffecter l'ID avec (Servlet 3).Par défaut dans les environnements 1 et supérieurs)

référence

Recommended Posts

Gestion des sessions de mémo d'utilisation de Spring Security
Mémo d'utilisation de Spring Security CSRF
Mémo d'utilisation de Spring Security Run-As
Spring Security Usage Memo Method Security
Mémo d'utilisation de Spring Security CORS
Test de mémo d'utilisation de Spring Security
Authentification / autorisation de mémo d'utilisation de Spring Security
En-tête de réponse de mémo d'utilisation de Spring Security
Mémo d'utilisation de Spring Security Basic / mécanisme
Spring Security Usage Memo Domain Object Security (ACL)
Notes d'utilisation de Spring Shell
Notes d'utilisation de JavaParser
Notes d'utilisation de WatchService
Mémo d'utilisation PlantUML
Notes d'utilisation de JUnit5
Mémo JJUG CCC Printemps 2018
À propos de l'authentification Spring Security
Rédaction de mémo de démarrage de printemps (1)
Spring Security soulève 403 interdits
Rédaction de mémos de démarrage de printemps (2)
[Notes personnelles] À propos du framework Spring
Mémo de participation au printemps JJUG CCC 2018
Série de mémos d'auto-apprentissage Spring Framework_1
Fonction de connexion avec Spring Security
[Spring Security] Spring Security sur GAE (SE)
Essayez d'utiliser Spring Boot Security
Mémo de méthode de contrôleur de démarrage à ressort