spring boot その9 - spring security で Remember-Me認証を行う
実装したソースは https://github.com/huruyosiathatena/springboot/tree/b5e82c5b8e24f4a7508d7fd86ca6cc311fe43adf にあります。
Remember Me 認証
ログイン画面にある にある「Remember Me」にチェックをつけてログインすることで、次回アクセス時に認証を省略できます。
動き
「Remember Me」にチェックをつけてログインを行うと、「remember-me」という名前でcookieが発行されます。cookieにはtokenが設定されており、spring-security内部でログインIDと対応付けられています。
ブラウザを閉じた後に、認証が必要がURLにアクセスした時にremember-meのcookieが送られてきたら spring-securityが対応付けられたログインIDを探して、登録されていれば認証が省略されます。
Remember Meのトークンの管理
デフォルトではMapを用いているorg.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl
クラスが利用されます。この方法では spring bootを再起動すると消えてしまうので、 org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl
クラスを利用して DB に保存します。
DBに保存するためのテーブルは JdbcTokenRepositoryImpl#setCreateTableOnStartup(boolean createTableOnStartup)
にtrueを指定することで自動的に作られます。しかし、テーブルが存在する場合には失敗するので、あらかじめ作成します。
DDLはリファレンス http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#remember-me-persistent-token に記載されています。
-- remember me 認証 create table persistent_logins( username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null );
認可 - @Secured
@Securedに指定するロールにIS_AUTHENTICATED_REMEMBERED
を指定することでRemember-Meの認証情報でアクセスが許可されます。
値 | 意味 |
---|---|
IS_AUTHENTICATED_ANONYMOUSLY | ログインしていなくても許可する |
IS_AUTHENTICATED_REMEMBERED | Remember-Me認証でアクセスを許可する |
IS_AUTHENTICATED_FULLY | 現在のセッションでログインしていれば許可する。Remember-Me認証でアクセスした場合には否認される。 |
実際にはロール名と併せて@Secured{"IS_AUTHENTICATED_REMEMBERED","ROLE_STAFF"})
という指定になると思います。
認可 - WebSecurityConfigurerAdapter#configure(HttpSecurity http)
HttpSecurityで設定する認証方法の指定も 上と同様に種類を使い分けます。
現在のセッションで認証が必要な場合には.fullyAuthenticated()
を使います。
http .authorizeRequests() .antMatchers("/sbadmin2/css/**").permitAll() .antMatchers("/sbadmin2/js/**").permitAll() .antMatchers("/sbadmin2/bower_components/**").permitAll() .anyRequest().fullyAuthenticated()
Remember-Me認証も許可する場合には.authenticated()
を使います。
http .authorizeRequests() .antMatchers("/sbadmin2/css/**").permitAll() .antMatchers("/sbadmin2/js/**").permitAll() .antMatchers("/sbadmin2/bower_components/**").permitAll() .anyRequest().authenticated()
テンプレートの記述
Remember-Meのチェックボックスは spring-securityの実装(org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices#rememberMeRequested(HttpServletRequest request, String parameter)
)に従う必要があります。
<input name="remember-me" type="checkbox" value="yes" />Remember Me
- name属性はremember-meでなければなりません。
- value属性は下のいずれかでなければなりません。
true
、on
、1
、yes
有効期間
デフォルトでは前回のアクセスから 2 週間は 有効です。変更する場合は WebSecurityConfigurerAdapter#configure(HttpSecurity http)
内でhttp.rememberMe().tokenValiditySeconds(604800)
という要領で指定します。tokenValiditySecondsに秒数を指定します。
テンプレートの実装
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="sbadmin2ContentOnly"> <head> </head> <body> <div layout:fragment="content"> <div class="container"> <div class="row"> <div class="col-md-4 col-md-offset-4"> <div class="login-panel panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Please Sign In</h3> </div> <div class="panel-body"> <div class="alert alert-warning" role="alert" th:if="${param.logout}">You have been logged out</div> <div class="alert alert-danger" role="alert" th:if="${param.error}">There was an error, please try again</div> <form role="form" method="post" th:action="@{/login}"> <fieldset> <div class="form-group"> <input class="form-control" placeholder="E-mail" name="userid" type="text" autofocus="" /> </div> <div class="form-group"> <input class="form-control" placeholder="Password" name="password" type="password" value="" /> </div> <div class="checkbox"> <label> <input name="remember-me" type="checkbox" value="yes" />Remember Me </label> </div> <!-- Change this to a button or input when using this as a form --> <input type="submit" value="login" class="btn btn-lg btn-success btn-block" /> </fieldset> </form> </div> </div> </div> </div> </div> </div> </body> </html>
WebSecurityConfigurerAdapter の実装
package com.hatenablog.huruyosi.springboot.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.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.hatenablog.huruyosi.springboot.service.JdbcUserDetailsServiceImpl; @Configuration @EnableWebMvcSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled=true) public class WebSecuirty extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private JdbcUserDetailsServiceImpl userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/sbadmin2/css/**").permitAll() .antMatchers("/sbadmin2/js/**").permitAll() .antMatchers("/sbadmin2/bower_components/**").permitAll() .anyRequest().authenticated() .and().formLogin() .loginPage("/login") .usernameParameter("userid") .passwordParameter("password") .defaultSuccessUrl("/sbadmin2/index.html") .failureUrl("/login?error").permitAll() .and().logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login?logout") .deleteCookies("JSESSIONID") .invalidateHttpSession(true).permitAll() .and().rememberMe() .tokenRepository(jdbcTokenRepository()) .tokenValiditySeconds(604800) // remember for a week. ( 1 * 60 * 60 * 24 * 7 ) sec ; } @Autowired public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } /** * Remember Me 認証に利用するトークンのリポジトリ * @return */ @Bean public PersistentTokenRepository jdbcTokenRepository() { JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl(); repository.setDataSource(dataSource); // repository.setCreateTableOnStartup(true); return repository; } }
コントローラーの実装
package com.hatenablog.huruyosi.springboot.controllers; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/sbadmin2/") @Secured("IS_AUTHENTICATED_REMEMBERED") public class SbAdmin2Controller { @RequestMapping("/index.html") public ModelAndView index() { return new ModelAndView("SbAdmin2Controller/index"); } ~ 省略 ~ }