Spring Security Remember Me Exemple

Exemple de rappel de sécurité de Spring

spring-security-remember-me

Dans ce didacticiel, nous vous montrerons comment implémenter la fonction de connexion «Se souvenir de moi» dans Spring Security, ce qui signifie que le système se souviendra de l'utilisateur et effectuera une connexion automatique même après l'expiration de la session de l'utilisateur.

Technologies et outils utilisés:

  1. Spring 3.2.8.RELEASE

  2. Spring Security 3.2.3.RELEASE

  3. Spring JDBC 3.2.3.RELEASE

  4. Eclipse 4.2

  5. JDK 1.6

  6. Maven 3

  7. MySQL Server 5.6

  8. Tomcat 6 et 7 (Servlet 3.x)

  9. Testez avec Google Chrome

Quelques notes rapides:

  1. Dans Spring Security, il existe deux approches pour implémenter "Remember me" - Simple Hash-Based Token et Persistent Token Approach.

  2. Pour comprendre comment fonctionne le «souvenir de moi», veuillez lire ces articles -Spring remember me reference,Persistent Login Cookie Best Practice,Improved Persistent Login Cookie Best Practice.

  3. Cet exemple utilise une «approche de jeton persistant», reportez-vous auxPersistentTokenBasedRememberMeServices de Spring.

  4. Cet exemple utilise MySQL et l'authentification de base de données (via Spring JDBC).

  5. Le tableau «persistent_logins» sera créé pour stocker le jeton de connexion et la série.

Flux de travail du projet:

  1. Si l'utilisateur se connecte avec une case «se souvenir de moi» cochée, le système stockera un cookie «se souvenir de moi» dans le navigateur demandé.

  2. Si le navigateur de l’utilisateur fournit un cookie «se souvenir de moi» valide, le système effectuera une connexion automatique.

  3. Si l'utilisateur se connecte via des cookies "se souvenir de moi", pour mettre à jour les détails de l'utilisateur, l'utilisateur doit taper à nouveau le nom d'utilisateur et le mot de passe (bonne pratique pour éviter le cookie volé pour mettre à jour les informations de l'utilisateur.

P.S This is a very high level of how “remember me” should work, for detail, please refer to the above links in “quick notes”.

1. Démo du projet

2. Répertoire des projets

Vérifiez la structure du répertoire du projet.

spring-security-remember-me-directory

3. Scripts MySQL

Scripts SQL pour créerusers,user_roles etpersistent_logins.

CREATE  TABLE users (
  username VARCHAR(45) NOT NULL ,
  password VARCHAR(45) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  PRIMARY KEY (username));

CREATE TABLE user_roles (
  user_role_id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  role varchar(45) NOT NULL,
  PRIMARY KEY (user_role_id),
  UNIQUE KEY uni_username_role (role,username),
  KEY fk_username_idx (username),
  CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username));

INSERT INTO users(username,password,enabled)
VALUES ('example','123456', true);

INSERT INTO user_roles (username, role)
VALUES ('example', 'ROLE_USER');
INSERT INTO user_roles (username, role)
VALUES ('example', 'ROLE_ADMIN');

CREATE TABLE persistent_logins (
    username varchar(64) not null,
    series varchar(64) not null,
    token varchar(64) not null,
    last_used timestamp not null,
    PRIMARY KEY (series)
);

4. Se souvenir de moi (exemple XML)

Pour activer «se souvenir de moi» dans la configuration XML, placez la baliseremember-me dans leshttp comme ceci:

spring-security.xml

  
  
    

    

    
    

    
    

  

spring-database.xml

  

    
    
    
    
  

  
  
  
    
  
  1. token-validity-seconds - La date d'expiration du cookie «se souvenir de moi», en secondes. Par exemple, 1209600 = 2 semaines (14 jours), 86400 = 1 jour, 18000 = 5 heures.

  2. remember-me-parameter - Le nom de la «case à cocher». La valeur par défaut est «_spring_security_remember_me».

  3. data-source-ref - Si cela est spécifié, «Persistent Token Approach» sera utilisé. La valeur par défaut est «Approche de jeton basée sur le hachage simple».

5. Se souvenir de moi (exemple d'annotation)

L'équivalent d'annotations:

SecurityConfig.java

package com.example.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;
    //...

    @Override
    protected void configure(HttpSecurity http) throws Exception {

      http.authorizeRequests()
          .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
        .and()
          .formLogin()
            .successHandler(savedRequestAwareAuthenticationSuccessHandler())
        .loginPage("/login")
            .failureUrl("/login?error")
        .loginProcessingUrl("/auth/login_check")
        .usernameParameter("username")
        .passwordParameter("password")
        .and()
        .logout().logoutSuccessUrl("/login?logout")
        .and()
            .csrf()
        .and()
        .rememberMe().tokenRepository(persistentTokenRepository())
        .tokenValiditySeconds(1209600);
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
        db.setDataSource(dataSource);
        return db;
    }

    @Bean
    public SavedRequestAwareAuthenticationSuccessHandler
                savedRequestAwareAuthenticationSuccessHandler() {

               SavedRequestAwareAuthenticationSuccessHandler auth
                    = new SavedRequestAwareAuthenticationSuccessHandler();
        auth.setTargetUrlParameter("targetUrl");
        return auth;
    }

}

P.S In annotation configuration, the default http name for “remember me” check box is “remember-me”.

6.HTML / JSP Pages

6.1 In JSP, you can use Spring security tag sec:authorize access="isRememberMe()" to determine if this user is login by “remember me” cookies.

admin.jsp

<%@taglib prefix="sec"
    uri="http://www.springframework.org/security/tags"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>


    

Title : ${title}

Message : ${message}

Welcome : ${pageContext.request.userPrincipal.name} | Logout

# This user is login by "Remember Me Cookies".

# This user is login by username / password.

6.2 A simple login form with “remember me” check box.

login.jsp

   
User:
Password:
Remember Me:

6.3 Update page. Seule la connexion de l'utilisateur à l'aide d'un mot de passe est autorisée à accéder à cette page.

update.jsp

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>


    

Title : Spring Security Remember Me Example - Update Form

Message : This page is for ROLE_ADMIN and fully authenticated only (Remember me cookie is not allowed!)

Update Account Information...

7. Manette

Classe de contrôleur de printemps, lisez le commentaire pour l'explication.

MainController.java

package com.example.web.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {

    @RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
    public ModelAndView defaultPage() {

        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring Security Remember Me");
        model.addObject("message", "This is default page!");
        model.setViewName("hello");
        return model;

    }

    @RequestMapping(value = "/admin**", method = RequestMethod.GET)
    public ModelAndView adminPage() {

        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring Security Remember Me");
        model.addObject("message", "This page is for ROLE_ADMIN only!");
        model.setViewName("admin");

        return model;

    }

    /**
     * This update page is for user login with password only.
     * If user is login via remember me cookie, send login to ask for password again.
     * To avoid stolen remember me cookie to update info
     */
    @RequestMapping(value = "/admin/update**", method = RequestMethod.GET)
    public ModelAndView updatePage(HttpServletRequest request) {

        ModelAndView model = new ModelAndView();

        if (isRememberMeAuthenticated()) {
            //send login for update
            setRememberMeTargetUrlToSession(request);
            model.addObject("loginUpdate", true);
            model.setViewName("/login");

        } else {
            model.setViewName("update");
        }

        return model;

    }

    /**
     * both "normal login" and "login for update" shared this form.
     *
     */
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView login(@RequestParam(value = "error", required = false) String error,
      @RequestParam(value = "logout", required = false) String logout,
          HttpServletRequest request) {

        ModelAndView model = new ModelAndView();
        if (error != null) {
            model.addObject("error", "Invalid username and password!");

            //login form for update page
                        //if login error, get the targetUrl from session again.
            String targetUrl = getRememberMeTargetUrlFromSession(request);
            System.out.println(targetUrl);
            if(StringUtils.hasText(targetUrl)){
                model.addObject("targetUrl", targetUrl);
                model.addObject("loginUpdate", true);
            }

        }

        if (logout != null) {
            model.addObject("msg", "You've been logged out successfully.");
        }
        model.setViewName("login");

        return model;

    }

    /**
     * Check if user is login by remember me cookie, refer
     * org.springframework.security.authentication.AuthenticationTrustResolverImpl
     */
    private boolean isRememberMeAuthenticated() {

        Authentication authentication =
                    SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return false;
        }

        return RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass());
    }

    /**
     * save targetURL in session
     */
    private void setRememberMeTargetUrlToSession(HttpServletRequest request){
        HttpSession session = request.getSession(false);
        if(session!=null){
            session.setAttribute("targetUrl", "/admin/update");
        }
    }

    /**
     * get targetURL from session
     */
    private String getRememberMeTargetUrlFromSession(HttpServletRequest request){
        String targetUrl = "";
        HttpSession session = request.getSession(false);
        if(session!=null){
            targetUrl = session.getAttribute("targetUrl")==null?""
                             :session.getAttribute("targetUrl").toString();
        }
        return targetUrl;
    }

}

8. Demo

8.1 Access protected page – http://localhost:8080/spring-security-remember-me/admin, the system will redirect user to a login form. Essayez de vous connecter en cochant "souviens-toi de moi".

spring-security-remember-me-example-0

spring-security-remember-me-example-2

8.2 In Google Chrome, Settings → Show advanced settings → Privacy, Content Setting… → “All cookies and site data” – there are two cookies for localhost, one for current session and one for “remember me” login cookies.

spring-security-remember-me-example-1

8.3 Review table “persistent_logins”, username, series and token is stored.

spring-security-remember-me-table

8.4 Restart the web application, go Chrome “All cookies and site data”, and remove the browser’s session “JSESSIONID”. Essayez d'accéder à nouveau à la page de connexion. Maintenant, le système «se souviendra de vous» et une connexion automatique via les cookies de connexion dans votre navigateur.

spring-security-remember-me-example-3

8.5 Try to access the “update” page – http://localhost:8080/spring-security-remember-me/admin/update, if user is login by remember me cookies, the system will redirect user to login form again. Il s'agit d'une bonne pratique pour éviter le cookie volé pour mettre à jour les détails de l'utilisateur.

spring-security-remember-me-example-4

8.6 Done.

spring-security-remember-me-example-5

9. Misc

Quelques cours de sécurité Spring importants à étudier:

  1. org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer.java

  2. org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.java

  3. org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices.java

  4. org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.java

  5. org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter

Télécharger le code source

Téléchargez-le -spring-security-remember-me.zip (18 Ko)

Téléchargez-le -spring-security-remember-me-annotation.zip (25 Ko)