Пример Spring Security Запомнить меня

Spring Security Запомнить меня Пример

spring-security-remember-me

В этом руководстве мы покажем вам, как реализовать функцию входа в систему «Запомнить меня» в Spring Security, а это означает, что система будет помнить пользователя и выполнять автоматический вход в систему даже после истечения сеанса пользователя.

Используемые технологии и инструменты:

  1. Весна 3.2.8. ВЫПУСК

  2. Spring Security 3.2.3.RELEASE

  3. Spring JDBC 3.2.3.RELEASE

  4. Затмение 4.2

  5. JDK 1.6

  6. Maven 3

  7. MySQL Server 5.6

  8. Tomcat 6 и 7 (сервлет 3.x)

  9. Тест с Google Chrome

Несколько быстрых заметок:

  1. В Spring Security есть два подхода для реализации «запомни меня» - простой хеш-ориентированный токен и подход с постоянными токенами.

  2. Чтобы понять, как работает «запомнить меня», прочтите эти статьи -Spring remember me reference,Persistent Login Cookie Best Practice,Improved Persistent Login Cookie Best Practice.

  3. В этом примере используется «подход с постоянным токеном», обратитесь к SpringPersistentTokenBasedRememberMeServices.

  4. Этот пример использует MySQL и аутентификацию базы данных (через Spring JDBC).

  5. Будет создана таблица «persistent_logins» для хранения токена входа и серии.

Рабочие процессы проекта:

  1. Если пользователь вошел в систему с пометкой «Запомнить меня», система сохранит файл «Запомнить меня» в запрошенном браузере.

  2. Если браузер пользователя предоставляет действительный файл cookie «Запомнить меня», система выполнит автоматический вход в систему.

  3. Если пользователь входит в систему с помощью файлов cookie «Запомнить меня», для обновления сведений о пользователе пользователю необходимо снова ввести имя пользователя и пароль (рекомендуется избегать кражи файлов cookie для обновления информации о пользователе.

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. Проект Демо

2. Каталог проектов

Просмотрите структуру каталогов проекта.

spring-security-remember-me-directory

3. MySQL Scripts

Скрипты SQL для созданияusers,user_roles иpersistent_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. Запомнить меня (пример XML)

Чтобы включить «запомнить меня» в конфигурации XML, поместите тегremember-me вhttp следующим образом:

spring-security.xml

  
  
    

    

    
    

    
    

  

spring-database.xml

  

    
    
    
    
  

  
  
  
    
  
  1. token-validity-seconds - Дата истечения срока действия cookie «запомнить меня» в секундах. Например, 1209600 = 2 недели (14 дней), 86400 = 1 день, 18000 = 5 часов.

  2. remember-me-parameter - Название «флажка». По умолчанию используется значение «_spring_security_remember_me».

  3. data-source-ref - Если это указано, будет использоваться «подход с постоянным токеном». По умолчанию используется «Простой подход на основе хэшей».

5. Запомнить меня (пример аннотации)

Эквивалент аннотаций:

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. Только логин пользователя с использованием пароля разрешен доступ к этой странице.

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. контроллер

Spring контроллера класса, читайте комментарий для самоочевидности.

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. Попробуйте войти с пометкой «запомнить меня».

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”. Попробуйте снова зайти на страницу входа. Теперь система «запомнит вас» и автоматически авторизуется через куки-файлы для входа в браузере

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. Это хорошая практика, чтобы избежать кражи cookie, чтобы обновить детали пользователя.

spring-security-remember-me-example-4

8.6 Done.

spring-security-remember-me-example-5

9. Misc

Некоторые важные классы Spring Security для изучения:

  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

Скачать исходный код

Скачать -spring-security-remember-me.zip (18 КБ)

Скачать -spring-security-remember-me-annotation.zip (25 КБ)