记住我功能,相信大家在一些网站已经用过,一些安全要求不高的都可以使用这个功能,方便快捷。
spring security针对该功能有两种实现方式,一种是简单的使用加密来保证基于 cookie 的 token 的安全,另一种是通过数据库或其它持久化存储机制来保存生成的 token。
下面是基于简单加密 token 的方法的实现,基于前篇的限制登录次数的功能之上加入remember me功能
项目结构如下:
基本的结构没有变化,主要在于一些类的修改和配置。

 一、修改SecurityConfig配置文件
package com.petter.config;
import com.petter.handler.CustomAuthenticationProvider;
import com.petter.service.CustomUserDetailsService;
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.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import javax.annotation.Resource;
/**
* 相当于spring-security.xml中的配置
* @author hongxf
* @since 2017-03-08 9:30
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private CustomAuthenticationProvider authenticationProvider;
@Resource
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
/**
* 配置权限要求
* 采用注解方式,默认开启csrf
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/dba/**").hasAnyRole("ADMIN", "DBA")
.and()
.formLogin().successHandler(savedRequestAwareAuthenticationSuccessHandler())
.loginPage("/login") //指定自定义登录页
.failureUrl("/login?error") //登录失败的跳转路径
.loginProcessingUrl("/auth/login_check") //指定了登录的form表单提交的路径,需与表单的action值保存一致,默认是login
.usernameParameter("user-name").passwordParameter("pwd")
.and()
.logout().logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf()
.and()
.rememberMe().rememberMeParameter("remember-me") //其实默认就是remember-me,这里可以指定更换
.tokenValiditySeconds(1209600)
.key("hongxf");
}
//使用remember-me必须指定UserDetailsService
@Override
protected UserDetailsService userDetailsService() {
return userDetailsService;
}
/**
* 这里是登录成功以后的处理逻辑
* 设置目标地址参数为targetUrl
* /auth/login_check?targetUrl=/admin/update
* 这个地址就会被解析跳转到/admin/update,否则就是默认页面
*
* 本示例中访问update页面时候会判断用户是手动登录还是remember-me登录的
* 如果是remember-me登录的则会跳转到登录页面进行手动登录再跳转
* @return
*/
@Bean
public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {
SavedRequestAwareAuthenticationSuccessHandler auth = new SavedRequestAwareAuthenticationSuccessHandler();
auth.setTargetUrlParameter("targetUrl");
return auth;
}
}

这里需要指出几点:

1、使用remember-me功能必须指定UserDetailsService
2、修改登录成功以后的逻辑,具体见注释
3、添加remember me的配置,key("hongxf"),这里的key用于加密,可以进行指定
二、修改admin.html,添加
<div sec:authorize="isRememberMe()">
<h2>该用户是通过remember me cookies登录的</h2>
</div>
<div sec:authorize="isFullyAuthenticated()">
<h2>该用户是通过输入用户名和密码登录的</h2>
</div>

用于展示

三、修改登录页面login.html
form表单需要进行相应的修改
<form name='loginForm' th:action="@{/auth/login_check(targetUrl=${session.targetUrl})}" method='POST'>
<table>
<tr>
<td>用户名:</td>
<td><input type='text' name='user-name' /></td>
</tr>
<tr>
<td>密码:</td>
<td><input type='password' name='pwd' /></td>
</tr>
<!-- 如果是进行更新操作跳转过来的页面则不显示记住我 -->
<div th:if="${loginUpdate} eq null">
<tr>
<td></td>
<td>记住我: <input type="checkbox" name="remember-me" /></td>
</tr>
</div>
<tr>
<td colspan='2'>
<input type="submit" value="提交" />
</td>
</tr>
</table>
</form>

注意action的值,首先请求路径是/auth/login_check,与SecurityConfig配置的loginProcessingUrl保持一致

/auth/login_check(targetUrl=${session.targetUrl})会被解析成/auth/login_check?targetUrl=XXX 其中targetUrl的从session中获取
四、编写update.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>update</title>
</head>
<body>
<h1>Title : 更新页面</h1>
<h1>只有通过用户名和密码登录的用户才允许进入这个页面,remember me登录的用户不允许,防止被盗用cookie</h1>
<h2>更新账号信息</h2>
</body>
</html>

五、修改HelloController类

package com.petter.web;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* @author hongxf
* @since 2017-03-08 9:29
*/
@Controller
public class HelloController {
@RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET)
public ModelAndView welcomePage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "This is welcome page!");
model.setViewName("hello");
return model;
}
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "This is protected page - Admin Page!");
model.setViewName("admin");
return model;
}
@RequestMapping(value = "/dba", method = RequestMethod.GET)
public ModelAndView dbaPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "This is protected page - Database Page!");
model.setViewName("admin");
return model;
}
/**
* 登录页面只允许使用密码登录
* 如果用户通过remember me的cookie登录则跳转到登录页面输入密码
* 为了避免盗用remember me cookie 来更新信息
*/
@RequestMapping(value = "/admin/update", method = RequestMethod.GET)
public ModelAndView updatePage(HttpServletRequest request) {
ModelAndView model = new ModelAndView();
if (isRememberMeAuthenticated()) {
//把targetUrl放入session中,登录页面使用${session.targetUrl}获取
setRememberMeTargetUrlToSession(request);
//跳转到登录页面
model.addObject("loginUpdate", true);
model.setViewName("login");
} else {
model.setViewName("update");
}
return model;
}
/**
* 判断用户是不是通过remember me方式登录,参考
* org.springframework.security.authentication.AuthenticationTrustResolverImpl
*/
private boolean isRememberMeAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass());
}
/**
* 保存请求的页面targetUrl到session中
*/
private void setRememberMeTargetUrlToSession(HttpServletRequest request){
HttpSession session = request.getSession(false);
if(session != null){
session.setAttribute("targetUrl", request.getRequestURI());
}
}
//获取session存储的SPRING_SECURITY_LAST_EXCEPTION的值,自定义错误信息
@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", getErrorMessage(request, "SPRING_SECURITY_LAST_EXCEPTION"));
//在update的登录页面上,判断targetUrl是否有值,没有则显示记住我,有则不显示
String targetUrl = getRememberMeTargetUrlFromSession(request);
System.out.println(targetUrl);
if(StringUtils.hasText(targetUrl)){
model.addObject("loginUpdate", true);
}
}
if (logout != null) {
model.addObject("msg", "你已经成功退出");
}
model.setViewName("login");
return model;
}
/**
* 从session中获取targetUrl
*/
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;
}
//自定义错误类型
private String getErrorMessage(HttpServletRequest request, String key){
Exception exception = (Exception) request.getSession().getAttribute(key);
String error;
if (exception instanceof BadCredentialsException) {
error = "不正确的用户名或密码";
}else if(exception instanceof LockedException) {
error = exception.getMessage();
}else{
error = "不正确的用户名或密码";
}
return error;
}
@RequestMapping(value = "/403", method = RequestMethod.GET)
public ModelAndView accessDenied() {
ModelAndView model = new ModelAndView();
//检查用户是否已经登录
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
UserDetails userDetail = (UserDetails) auth.getPrincipal();
model.addObject("username", userDetail.getUsername());
}
model.setViewName("403");
return model;
}
}

六、进行测试

启动应用,访问http://localhost:8080/admin 会跳转到登录页

 输入正确的用户名和密码,勾选记住我,登录成功进入admin页面
 

 

可以查看此时的cookie中的值

 

 可以看到remember-me的值以及失效日期
想要尝试记住我免登录功能,重启应用,访问http://localhost:8080/admin ,可以看到
 

 现在尝试访问http://localhost:8080/admin/update 则会跳转到登录页面
 

 注意上面的访问地址就是http://localhost:8080/admin/update 只是返回的是登录页内容,并且隐藏了记住我选项,查看页面源代码,可以看到
 
<form name='loginForm' action="/auth/login_check?targetUrl=/admin/update" method='POST'>
当输入争取的用户名和密码时候security会根据targetUrl的值跳转到之前访问的页面

 
 
 
 

spring security采用基于简单加密 token 的方法实现的remember me功能的更多相关文章

  1. spring security采用基于持久化 token 的方法实现的remember me功能

    采用该方法相较于简单加密方式安全一些.具体的原理见 http://wiki.jikexueyuan.com/project/spring-security/remember-me.html  一.建立 ...

  2. 255.Spring Boot+Spring Security:使用md5加密

    说明 (1)JDK版本:1.8 (2)Spring Boot 2.0.6 (3)Spring Security 5.0.9 (4)Spring Data JPA 2.0.11.RELEASE (5)h ...

  3. Spring security oauth2最简单入门环境搭建

    关于OAuth2的一些简介,见我的上篇blog:http://wwwcomy.iteye.com/blog/2229889 PS:貌似内容太水直接被鹳狸猿干沉.. 友情提示 学习曲线:spring+s ...

  4. 关于 Spring Security OAuth2 中 Feign 调用 Token 问题

    微服务体系中,避免不了服务之间链式调用,一般使用 Feign ,由于使用 Spring Security OAuth2 全局做了安全认证,简单的一种实现方式就是在服务提供方获得 Token 再次通过 ...

  5. spring security采用自定义登录页和退出功能

    更新... 首先采用的是XML配置方式,请先查看  初识Spring security-添加security 在之前的示例中进行代码修改 项目结构如下: 一.修改spring-security.xml ...

  6. 使用Spring Security OAuth2进行简单的单点登录

    1.概述 在本教程中,我们将讨论如何使用Spring Security OAuth和Spring Boot实现SSO - 单点登录. 我们将使用三个单独的应用程序: 授权服务器 - 这是中央身份验证机 ...

  7. spring security +MySQL + BCryptPasswordEncoder 单向加密验证 + 权限拦截 --- 心得

    1.前言 前面学习了 security的登录与登出 , 但是用户信息 是 application 配置 或内存直接注入进去的 ,不具有实用性,实际上的使用还需要权限管理,有些 访问接口需要某些权限才可 ...

  8. Spring Security实现基于RBAC的权限表达式动态访问控制

    昨天有个粉丝加了我,问我如何实现类似shiro的资源权限表达式的访问控制.我以前有一个小框架用的就是shiro,权限控制就用了资源权限表达式,所以这个东西对我不陌生,但是在Spring Securit ...

  9. Spring Security入门(2-3)Spring Security 的运行原理 4 - 自定义登录方法和页面

    参考链接,多谢作者: http://blog.csdn.net/lee353086/article/details/52586916 http元素下的form-login元素是用来定义表单登录信息的. ...

随机推荐

  1. knowledgeroot 的配置与优化

    首先下载 KnowledgeRoot 的安装包,就是一个压缩文件,解压缩后放到 WebRoot 下面 在浏览器中打开网站,自动提示进行安装,安装的过程很简单,安装结束后即可以使用. 安装包创建的数据库 ...

  2. GetDesktopWindow和GetWindow区别

    GetWindow The GetWindow function retrieves a handle to a window that has the specified relationship ...

  3. 160621、Java注解教程及自定义注解

    Java注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容.在这个教程当中,我们将学习Java的注解,如何定制注解,注解的使用以及如何通过反射解析注解. Java1.5引入了注解,当前许 ...

  4. APP上传应用商店加固后打包

    在cmd进入jdk的bin目录,把keystore文件和apk安装包放到bin目录下,然后执行以下命令,需要管理员权限: jarsgner -verbose -sigalg SHA1withRSA - ...

  5. Elasticsearch集群 管理

    第7章 深入Elasticsearch集群 启动一个Elasticsearch节点时,该节点会开始寻找具有相同集群名字并且可见的主节点.如 果找到主节点,该节点加入一个已经组成了的集群:如果没有找到, ...

  6. <2014 05 21> 互联网时代的C语言——Go(2)

    **************************************************************************************************** ...

  7. jq封装选项卡写法

    jq普通选项卡写法: var tabTag=$('#tabon'); var tabon=tabTag.find('li');//菜单栏 var tabCon=$(".hidden" ...

  8. DHTML 简介

    DHTML, 动态的 html, 不是一门语言, 是多项技术综合体的简称.其中包括了 html, CSS, DOM, javascript. HTML : 负责提供标签, 对数据进行封装,目的是便于对 ...

  9. 原生js实现ajax方法

    下面是一个比较完整的Ajax function ajax(){ var ajaxData = { type:arguments[0].type || "GET", url:argu ...

  10. sql语句select group by order by where一般先后顺序

    写的顺序:select ... from... where.... group by... having... order by..执行顺序:from... where...group by... h ...