春のセキュリティ

Spring Security Remember Meの例

spring-security-remember-me

このチュートリアルでは、Spring Securityで「Remember Me」ログイン機能を実装する方法を示します。つまり、システムはユーザーを記憶し、ユーザーのセッションが期限切れになった後でも自動ログインを実行します。

使用される技術とツール:

  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. メーベン3

  7. MySQLサーバー5.6

  8. Tomcat 6および7(サーブレット3.x)

  9. Google Chromeでテストする

簡単なメモ:

  1. Spring Securityでは、「remember me」を実装するための2つのアプローチがあります。シンプルなハッシュベースのトークンと永続的なトークンのアプローチです。

  2. 「rememberme」がどのように機能するかを理解するには、次の記事をお読みください–Spring remember me referencePersistent Login Cookie Best PracticeImproved Persistent Login Cookie Best Practice

  3. この例では、「永続トークンアプローチ」を使用しています。SpringのPersistentTokenBasedRememberMeServicesを参照してください。

  4. この例では、MySQLとデータベース認証(Spring JDBC経由)を使用しています。

  5. ログイントークンとシリーズを保存するために、テーブル「persistent_logins」が作成されます。

プロジェクトのワークフロー:

  1. 「remember me」にチェックマークを付けてユーザーログインした場合、システムは要求されたブラウザに「remember me」Cookieを保存します。

  2. ユーザーのブラウザが有効な「remember me」Cookieを提供する場合、システムは自動ログインを実行します。

  3. ユーザーが「remember me」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スクリプト

usersuser_roles、およびpersistent_loginsを作成するSQLスクリプト。

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. Remember Me(XMLの例)

XML構成で「rememberme」を有効にするには、次のようにremember-meタグをhttpに配置します。

spring-security.xml

  
  
    

    

    
    

    
    

  

spring-database.xml

  

    
    
    
    
  

  
  
  
    
  
  1. token-validity-seconds –「remember-me」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. 「remember-me」にチェックマークを付けてログインしてみてください。

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”. ログインページに再度アクセスしてみてください。 これで、システムは「記憶」し、ブラウザのログインCookieを介して自動ログインします。

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 KB)

ダウンロード–spring-security-remember-me-annotation.zip(25 KB)