在本教程中,我们将向您展示如何在Spring Security中实现“记住我”登录功能,这意味着即使用户会话期满后,系统也将记住用户并执行自动登录。
使用的技术和工具:
- 春天3.2.8。发布
- Spring Security 3.2.3发布
- 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中,有两种实现“记住我”的方法-简单基于哈希的令牌和持久令牌方法。
- 要了解“记住我”的工作方式,请阅读这些文章-Spring记住我参考 , 持久性登录Cookie最佳实践 , 改进的持久性登录Cookie最佳实践 。
- 本示例使用“ Persistent Token Approach”,请参考Spring的
PersistentTokenBasedRememberMeServices
。 - 此示例使用MySQL和数据库身份验证(通过Spring JDBC)。
- 将创建表“ persistent_logins”以存储登录令牌和系列。
项目工作流程:
- 如果用户登录时选中了“记住我”,则系统将在请求的浏览器中存储“记住我” cookie。
- 如果用户的浏览器提供有效的“记住我” cookie,系统将执行自动登录。
- 如果用户通过“记住我” cookie登录,则要更新用户详细信息,用户需要再次输入用户名和密码(避免被盗的cookie来更新用户信息的良好做法。
PS:“记住我”的工作方式非常高,有关详细信息,请参阅“快速说明”中的上述链接。
1.项目演示
2.项目目录
查看项目目录结构。
3. MySQL脚本
创建users
, user_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 ('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.记住我(XML示例)
要在XML配置中启用“记住我”功能, remember-me
在http
放置“记住我”标记,如下所示:
<!-- 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>
<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 –“记住我” cookie的过期日期(以秒为单位)。 例如,1209600 = 2周(14天),86400 = 1天,18000 = 5小时。
- 记住我参数 –“复选框”的名称。 默认为'_spring_security_remember_me'。
- data-source-ref –如果指定此选项,将使用“持久令牌方法”。 默认为“基于简单哈希的令牌方法”。
5.记住我(注释示例)
相当于注解:
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;
}
}
PS在注释配置中,“记住我”复选框的默认http名称是“记住我”。
6.HTML / JSP页面
6.1在JSP中,您可以使用Spring安全性标签sec:authorize access="isRememberMe()"
来确定该用户是否通过“记住我” cookie登录。
<%@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带有“记住我”复选框的简单登录表单。
<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更新页面。 仅允许使用密码登录的用户访问此页面。
<%@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控制器类,请阅读注释以进行自我解释。
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 ,系统会将用户重定向到登录表单。 尝试登录并选中“记住我”。
8.2在Google Chrome浏览器中,设置->显示高级设置->隐私,内容设置...->“所有cookie和站点数据” –有两个用于localhost的cookie,一个用于当前会话,一个用于“记住我”登录cookie。
8.3检查表“ persistent_logins”,用户名,系列和令牌已存储。
8.4重新启动Web应用程序,转到Chrome“所有cookie和站点数据”,然后删除浏览器的会话“ JSESSIONID”。 尝试再次访问登录页面。 现在,系统将“记住您”并通过浏览器中的登录cookie自动登录。
8.5尝试访问“更新”页面-http:// localhost:8080 / spring-security-remember-me / admin / update ,如果用户通过记住我的cookie登录,系统将再次将用户重定向到登录表单。 这是避免被窃取的cookie来更新用户详细信息的好习惯。
8.6完成。
9.其他
一些重要的Spring Security课程需要学习:
- 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)
参考文献
- Spring Security记住我的参考
- 永久登录Cookie最佳做法
- 改进的持久登录Cookie最佳做法
- 对网站实施“记住我”的最佳方法是什么?
- Spring JdbcTemplate JavaDoc
- 使用数据库登录Spring Security表单– XML和注释示例
翻译自: https://mkyong.com/spring-security/spring-security-remember-me-example/