春のセキュリティ

このチュートリアルでは、Spring Securityの「Remember Me」ログイン機能を実装する方法について説明します。これは、システムがユーザを覚えて、ユーザのセッションが終了しても自動ログインを実行することを意味します。
使用される技術とツール:
-
Spring 3.2.8.RELEASE
-
春のセキュリティ3.2.3.RELEASE
-
Spring JDBC 3.2.3.RELEASE
-
Eclipse 4.2
-
JDK 1.6
-
Maven 3
-
MySQLサーバ5.6
-
Tomcat 6および7(Servlet 3.x)
-
Google Chromeでテストする
いくつかの簡単なメモ:
-
Spring Securityでは、2つのアプローチが実装されています.
me ” – 単純なハッシュベースのトークンと持続的なトークンアプローチ。
-
「私を覚えている」がどのように機能するかを理解するには、これらの記事をお読みください
-
この例では、「Persistent Token Approach」を使用しています. これは、Springの
`PersistentTokenBasedRememberMeServices`です。
-
この例では、MySQLとデータベース認証を使用しています(Spring
JDBC)。
-
テーブル「persistent__logins」が作成され、ログイントークンを格納します.
シリーズ。
プロジェクトのワークフロー:
-
ユーザーが「私を覚えてください」チェックボックスをオンにすると、システムは
リクエストされたブラウザの「私を覚えている」クッキー。
-
ユーザーのブラウザが有効な「Remember Me」Cookieを提供している場合、システム
自動ログインを実行します。
-
ユーザーが「私を覚えている」Cookieを使用してログインしている場合、ユーザーの詳細を更新するには、
ユーザーはユーザー名とパスワードを再度入力する必要があります(ユーザー情報を更新するために盗まれたCookieを避けるための良い方法です)。
__P.Sこれは、 “覚えておいてください”ということは非常に高いレベルです。詳しくは上記のリンクを参照してください。
1.プロジェクトデモ
2.プロジェクトディレクトリ
プロジェクトのディレクトリ構造を確認します。

3. MySQLスクリプト
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 ('mkyong','123456', true);
INSERT INTO user__roles (username, role)
VALUES ('mkyong', 'ROLE__USER');
INSERT INTO user__roles (username, role)
VALUES ('mkyong', '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設定で「私を覚えている」を有効にするには、
remember-me`タグを
http`に次のように入れます:
spring-security.xml
<!-- enable use-expressions -->
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/admin** ** " access="hasRole('ROLE__ADMIN')"/>
<form-login login-page="/login"
default-target-url="/welcome"
authentication-failure-url="/login?error"
username-parameter="username"
password-parameter="password"
login-processing-url="/auth/login__check"
authentication-success-handler-ref="savedRequestAwareAuthenticationSuccessHandler"/>
<logout logout-success-url="/login?logout" delete-cookies="JSESSIONID"/>
<csrf/>
<!-- enable remember me -->
<remember-me
token-validity-seconds="1209600"
remember-me-parameter="remember-me"
data-source-ref="dataSource"/>
</http>
spring-database.xml
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
<!-- If request parameter "targetUrl" is existed, then forward to this url -->
<!-- For update login form -->
<bean id="savedRequestAwareAuthenticationSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="targetUrlParameter" value="targetUrl"/>
</bean>
-
token-validity-seconds
– “remember-me”クッキーの有効期限
秒。たとえば、1209600 = 2週間(14日)、86400 = 1日、18000
= 5時間。
-
remember-me-parameter
– “チェックボックス”の名前. デフォルトは
‘
spring
security
remember
me’。
-
data-source-ref
– これが指定されている場合は、 “永続トークンアプローチ”
使用されます。デフォルトは「単純ハッシュ・ベースのトークン・アプローチ」です。
5.私を覚えている(注釈の例)
アノテーションに相当する:
SecurityConfig.java
package com.mkyong.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注釈設定では、 “remember me”チェックボックスのデフォルトのhttp名は “remember-me”です。
6.HTML/JSPページ
6.1 JSPで、Springセキュリティタグ `sec:authorize access =” isRememberMe() “`を使用して、このユーザーが「Remember me」Cookieでログインしているかどうかを判断できます。
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"%>
<html>
<body>
<h1>Title : ${title}</h1>
<h1>Message : ${message}</h1>
<c:url value="/j__spring__security__logout" var="logoutUrl"/>
<form action="${logoutUrl}" method="post" id="logoutForm">
<input type="hidden" name="${__csrf.parameterName}"
value="${__csrf.token}"/>
</form>
<script>
function formSubmit() {
document.getElementById("logoutForm").submit();
}
</script>
<c:if test="${pageContext.request.userPrincipal.name != null}">
<h2>
Welcome : ${pageContext.request.userPrincipal.name} | <a
href="javascript:formSubmit()"> Logout</a>
</h2>
</c:if>
<sec:authorize access="isRememberMe()">
<h2># This user is login by "Remember Me Cookies".</h2>
</sec:authorize>
<sec:authorize access="isFullyAuthenticated()">
<h2># This user is login by username/password.</h2>
</sec:authorize>
</body>
</html>
6.2「私を覚えてください」チェックボックスが付いた簡単なログインフォーム。
login.jsp
<form name='loginForm'
action="<c:url value='/auth/login__check?targetUrl=${targetUrl}'/>"
method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username'></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password'/></td>
</tr>
<!-- if this is login for update, ignore remember me check -->
<c:if test="${empty loginUpdate}">
<tr>
<td></td>
<td>Remember Me: <input type="checkbox" name="remember-me"/></td>
</tr>
</c:if>
<tr>
<td colspan='2'><input name="submit" type="submit"
value="submit"/></td>
</tr>
</table>
<input type="hidden" name="${__csrf.parameterName}"
value="${__csrf.token}"/>
</form>
6.3更新ページ。このページには、パスワードを使用したユーザーログインのみが許可されています。
update.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
<h1>Title : Spring Security Remember Me Example - Update Form</h1>
<h1>Message : This page is for ROLE__ADMIN and fully authenticated only
(Remember me cookie is not allowed!)</h1>
<h2>Update Account Information...</h2>
</body>
</html>
7.コントローラー
Springのコントローラクラスでは、コメントを読んで自明です。
MainController.java
package com.mkyong.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.デモ
8.1アクセス保護されたページ –
http://localhost:8080/spring-security-remember-me/admin
、システムはユーザをログインフォームにリダイレクトします。 「remember-me」をチェックしてログインしてみてください。


8.2 Google Chromeでは、設定 – >詳細設定を表示 – >プライバシー、コンテンツ設定 – >「すべてのCookieとサイトデータ」 – ローカルホスト用に2つ、現在のセッション用に1つ、「Remember me」ログインCookie用に。

8.3レビューテーブル “persistent__logins”、ユーザ名、シリーズ、トークンが保存されています。

8.4 Webアプリケーションを再起動し、Chromeの「すべてのCookieとサイトのデータ」に行き、ブラウザのセッション「JSESSIONID」を削除します。再度ログインページにアクセスしてみてください。これで、システムはブラウザを使用してログインCookieを使用して自動的にログインします。

8.5「アップデート」ページ(
http://localhost:8080/spring-security-remember-me/admin/update
)にアクセスしようとすると、ユーザーがクッキーを覚えてログインすると、システムはユーザーを再度ログインフォームにリダイレクトします。これは、ユーザーの詳細を更新するために盗まれたCookieを回避するための良い方法です。

8.6完了。

9.その他
勉強するいくつかの重要な春のセキュリティクラス:
-
org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer.java
-
org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.java
-
org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices.java
-
org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.java
-
org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
ソースコードをダウンロードする
ダウンロードする –
spring-security-remember-me.zip
(18 KB)
ダウンロードする –
spring-security-remember-me-annotation.zip
(25 KB)
参考文献
セキュリティ私を参照してください]。
http://fishbowl.pastiche.org/2004/01/19/persistent
login
cookie
best
practice/[Persistent
ログインCookieのベストプラクティス]。
http://jaspan.com/improved
persistent
login
cookie
best__practice[Improved
永続的なログインCookieのベストプラクティス]。
http://stackoverflow.com/questions/244882/what-is-the-best-way-to-implement-remember-me-for-a-website
[What
ウェブサイトの “私を覚えて”実装する最善の方法は何ですか?]。
http://docs.spring.io/spring-framework/docs/4.0.x/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html
[Spring
JdbcTemplate JavaDoc]。リンク://spring-security/spring-security-form-login-using-database/[Spring
データベース – XMLを使用したセキュリティフォームログインと注釈の例]