Spring Security记住我的例子

春天安全记住我

在本教程中,我们将向您展示如何在Spring Security中实现“记住我”登录功能,这意味着即使用户会话期满后,系统也将记住用户并执行自动登录。

使用的技术和工具:

  1. 春天3.2.8。发布
  2. Spring Security 3.2.3发布
  3. Spring JDBC 3.2.3.RELEASE
  4. Eclipse 4.2
  5. JDK 1.6
  6. Maven 3
  7. MySQL服务器5.6
  8. Tomcat 6和7(Servlet 3.x)
  9. 使用Google Chrome进行测试

一些快速注意事项:

  1. 在Spring Security中,有两种实现“记住我”的方法-简单基于哈希的令牌和持久令牌方法。
  2. 要了解“记住我”的工作方式,请阅读这些文章-Spring记住我参考持久性登录Cookie最佳实践改进的持久性登录Cookie最佳实践
  3. 本示例使用“ Persistent Token Approach”,请参考Spring的PersistentTokenBasedRememberMeServices
  4. 此示例使用MySQL和数据库身份验证(通过Spring JDBC)。
  5. 将创建表“ persistent_logins”以存储登录令牌和系列。

项目工作流程:

  1. 如果用户登录时选中了“记住我”,则系统将在请求的浏览器中存储“记住我” cookie。
  2. 如果用户的浏览器提供有效的“记住我” cookie,系统将执行自动登录。
  3. 如果用户通过“记住我” cookie登录,则要更新用户详细信息,用户需要再次输入用户名和密码(避免被盗的cookie来更新用户信息的良好做法。

PS:“记住我”的工作方式非常高,有关详细信息,请参阅“快速说明”中的上述链接。

1.项目演示

2.项目目录

查看项目目录结构。

Spring安全记住我目录

3. MySQL脚本

创建usersuser_rolespersistent_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-mehttp放置“记住我”标记,如下所示:

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>
  1. token-validity-seconds –“记住我” cookie的过期日期(以秒为单位)。 例如,1209600 = 2周(14天),86400 = 1天,18000 = 5小时。
  2. 记住我参数 –“复选框”的名称。 默认为'_spring_security_remember_me'。
  3. 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;
	}	
	
}

PS在注释配置中,“记住我”复选框的默认http名称是“记住我”。

6.HTML / JSP页面

6.1在JSP中,您可以使用Spring安全性标签sec:authorize access="isRememberMe()"来确定该用户是否通过“记住我” 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 ,系统会将用户重定向到登录表单。 尝试登录并选中“记住我”。

Spring安全记住我的例子0
Spring安全记住我的例子2

8.2在Google Chrome浏览器中,设置->显示高级设置->隐私,内容设置...->“所有cookie和站点数据” –有两个用于localhost的cookie,一个用于当前会话,一个用于“记住我”登录cookie。

Spring安全记住我的例子1

8.3检查表“ persistent_logins”,用户名,系列和令牌已存储。

春天安全记得我表

8.4重新启动Web应用程序,转到Chrome“所有cookie和站点数据”,然后删除浏览器的会话“ JSESSIONID”。 尝试再次访问登录页面。 现在,系统将“记住您”并通过浏览器中的登录cookie自动登录。

Spring安全记住我的例子3

8.5尝试访问“更新”页面-http:// localhost:8080 / spring-security-remember-me / admin / update ,如果用户通过记住我的cookie登录,系统将再次将用户重定向到登录表单。 这是避免被窃取的cookie来更新用户详细信息的好习惯。

Spring安全记住我的例子4

8.6完成。

Spring安全记住我的例子5

9.其他

一些重要的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)

参考文献

  1. Spring Security记住我的参考
  2. 永久登录Cookie最佳做法
  3. 改进的持久登录Cookie最佳做法
  4. 对网站实施“记住我”的最佳方法是什么?
  5. Spring JdbcTemplate JavaDoc
  6. 使用数据库登录Spring Security表单– XML和注释示例

翻译自: https://mkyong.com/spring-security/spring-security-remember-me-example/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值