本文源码请看这里

相关文章:

Spring Security4实例(Java config版)——ajax登录,自定义验证


Spring Security提供了两种remember-me的实现,一种是简单的使用加密来保证基于cookie的token的安全,另一种是通过数据库或其它持久化存储机制来保存生成的token。


一、简单Hash-Based Token方式

  1. 首先在登录页login.html上添加一个CheckBox控件:

    <tr>
    <td>rememberMe:</td>
    <td><input type="checkbox" name='remember-me' id='rememberMe' /></td>
    </tr>
  2. 配置WebSecurityConfig类中的configure(HttpSecurity http)方法,开启remember me功能:

    ```

    @Override

    protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests().antMatchers("/", "/login/**").permitAll()

    // user权限可以访问的请求

    .antMatchers("/security/user").hasRole("user")

    // admin权限可以访问的请求

    .antMatchers("/security/admin").hasRole("admin")

    //SpEL表达式:需要拥有user权限,且进行了完全认证

    .antMatchers("/user/account").access("hasRole('user') and isFullyAuthenticated()")

    // 其他地址的访问均需验证权限(需要登录)

    .anyRequest().authenticated().and()

    // 添加验证码验证

    .addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).exceptionHandling()

    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login_page")).and()

    // 指定登录页面的请求路径

    .formLogin().loginPage("/login_page")

    // 登陆处理路径

    .loginProcessingUrl("/login").permitAll().and()

    //退出请求的默认路径为logout,下面改为signout, 成功退出登录后的url可以用logoutSuccessUrl设置

    .logout().logoutUrl("/signout").logoutSuccessUrl("/login_page").permitAll().and()

    // 开启rememberMe,设置一个私钥专供testall项目使用,注意与下面TokenBasedRememberMeServices的key保持一致

    .rememberMe().key("testallKey").and()

    // 关闭csrf

    .csrf().disable();

    }


倒数第二行代码就是啦,还可以用tokenValiditySeconds设置一个cookie的过期时间,单位为秒;
如果没有扩展UsernamePasswordAuthenticationFilter类,定制自己的Filte(比如为了加个验证码),上面的配置应该就可以了。否则还得继续配置:

---
3. 设置TokenBasedRememberMeServices Bean,可以在里面进行一些remember me的配置:
@Bean
public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
    TokenBasedRememberMeServices tbrms = new TokenBasedRememberMeServices("testallKey", userDetailsServiceImpl());
    // 设置cookie过期时间为2天
    tbrms.setTokenValiditySeconds(60 * 60 * 24 * 2);
    // 设置checkbox的参数名为rememberMe(默认为remember-me),注意如果是ajax请求,参数名不是checkbox的name而是在ajax的data里
    tbrms.setParameter("rememberMe");
    return tbrms;
}

注意:这时候在步骤2里设置过期时间等配置均无效,但是key("testallKey")还是要设置的。

4. 在MyUsernamePasswordAuthenticationFilter Bean中设置RememberMeServices为上面的service,还是倒数第二行

@Bean

public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {

MyUsernamePasswordAuthenticationFilter myFilter = new MyUsernamePasswordAuthenticationFilter();

myFilter.setAuthenticationManager(authenticationManagerBean());

myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());

myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());

myFilter.setRememberMeServices(tokenBasedRememberMeServices());

return myFilter;

}

这样就可以了,下面是部分说明

---
- 当用户选择了记住我成功登录后,Spring Security将会生成一个cookie发送给客户端浏览器。cookie值由如下方式组成:

base64(username+":"+expirationTime+":"+md5Hex(username+":"+expirationTime+":"+password+":"+key))

Øusername:登录的用户名。

Øpassword:登录的密码。

ØexpirationTime:token失效的日期和时间,以毫秒表示。

Økey:用来防止修改token的一个key。

这样用来实现Remember-Me功能的token只能在指定的时间内有效,且必须保证token中所包含的username、password和key没有被改变才行。需要注意的是,这样做其实是存在安全隐患的,那就是在用户获取到实现记住我功能的token后,任何用户都可以在该token过期之前通过该token进行自动登录。如果用户发现自己的token被盗用了,那么他可以通过改变自己的登录密码来立即使其所有的记住我token失效。如果希望我们的应用能够更安全一点,可以使用接下来要介绍的持久化token方式,或者不使用Remember-Me功能,因为Remember-Me功能总是有点不安全的。

- 下面的图表阐述了校验remember me cookie过程中涉及到的不同组件:
![4faa6213-743c-3975-bd65-6f2974f44640](https://user-images.githubusercontent.com/24689696/28108014-9bb08990-671c-11e7-8b13-25815cac327c.png)

- [TokenBasedRememberMeServices](http://docs.spring.io/spring-security/site/docs/4.2.3.RELEASE/reference/htmlsingle/#tokenbasedremembermeservices)官方文档的说明

> This implementation supports the simpler approach described in Section 17.2, “Simple Hash-Based Token Approach”. TokenBasedRememberMeServices generates a RememberMeAuthenticationToken, which is processed by RememberMeAuthenticationProvider. A key is shared between this authentication provider and the TokenBasedRememberMeServices. In addition, TokenBasedRememberMeServices requires A UserDetailsService from which it can retrieve the username and password for signature comparison purposes...

The beans required in an application context to enable remember-me services are as follows:

> Don’t forget to add your RememberMeServices implementation to your UsernamePasswordAuthenticationFilter.setRememberMeServices() property, include the RememberMeAuthenticationProvider in your AuthenticationManager.setProviders() list, and add RememberMeAuthenticationFilter into your FilterChainProxy (typically immediately after your UsernamePasswordAuthenticationFilter).

大致翻译如下:
这个实现类就对应于17.2章节描述的那个“Simple Hash-Based Token Approach”(就是上面的简单Hash-Based Token方式)。TokenBasedRememberMeServices(以下用“它”代替) 生成了一个RememberMeAuthenticationToken(它的抽象父类AbstractRememberMeServices中的方法createSuccessfulAuthentication实现的),以供RememberMeAuthenticationProvider处理。一个key被这个AuthenticationProvider和它共享使用。此外,它还需要一个UserDetailsService 用来获取用户名和密码进行签名对比...

这个bean需要进行如下配置:
见上xml

最后别忘了,需要配置UsernamePasswordAuthenticationFilter的rememberMeServices为我们定义好的TokenBasedRememberMeServices,把RememberMeAuthenticationProvider加入AuthenticationManager的providers列表,并添加RememberMeAuthenticationFilter和UsernamePasswordAuthenticationFilter到FilterChainProxy。(通常紧接着放在UsernamePasswordAuthenticationFilter后面)。

所以上面的配置好像跟这里描述的不太一样(不过效果倒是一样),接下来按照官方文档描述的修改一下配置(上面第1步不变,从第二步开启remember me配置开始修改):

---

2. 注释掉rememberMe(),在myUsernamePasswordAuthenticationFilter后添加rememberMeAuthenticationFilter

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests().antMatchers("/", "/login/**").permitAll()

// user权限可以访问的请求

.antMatchers("/security/user").hasRole("user")

// admin权限可以访问的请求

.antMatchers("/security/admin").hasRole("admin")

// SpEL表达式:需要拥有user权限,且进行了完全认证

.antMatchers("/user/account").access("hasRole('user') and isFullyAuthenticated()")

// 其他地址的访问均需验证权限(需要登录)

.anyRequest().authenticated().and()

// 添加验证码验证

.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).exceptionHandling()

.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login_page")).and()

.addFilterAt(rememberMeAuthenticationFilter(),RememberMeAuthenticationFilter.class)

// 指定登录页面的请求路径

.formLogin().loginPage("/login_page")

// 登陆处理路径

.loginProcessingUrl("/login").permitAll().and()

// 退出请求的默认路径为logout,下面改为signout,

// 成功退出登录后的url可以用logoutSuccessUrl设置

.logout().logoutUrl("/signout").logoutSuccessUrl("/login_page").permitAll().and()

// 开启rememberMe,设置一个私钥专供testall项目使用,注意与下面TokenBasedRememberMeServices的key保持一致

//.rememberMe().key("testallKey").and()

// 关闭csrf

.csrf().disable();

}


3.  上面的3、4步还是需要的,此外还要加上之前没有配置的rememberMeAuthenticationProvider和rememberMeAuthenticationFilter,下面就直接把相关代码都贴上了

@Bean

public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {

MyUsernamePasswordAuthenticationFilter myFilter = new MyUsernamePasswordAuthenticationFilter();

myFilter.setAuthenticationManager(authenticationManagerBean());

myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());

myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());

myFilter.setRememberMeServices(tokenBasedRememberMeServices());

return myFilter;

}

@Bean
public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
    TokenBasedRememberMeServices tbrms = new TokenBasedRememberMeServices("testallKey", userDetailsServiceImpl());
    // 设置cookie过期时间为2天
    tbrms.setTokenValiditySeconds(60 * 60 * 24 * 2);
    // 设置checkbox的参数名为rememberMe(默认为remember-me),注意如果是ajax请求,参数名不是checkbox的name而是在ajax的data里
    tbrms.setParameter("rememberMe");
    return tbrms;
}

@Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
    RememberMeAuthenticationProvider rmap = new RememberMeAuthenticationProvider("testallKey");
    return rmap;
}

@Bean
public RememberMeAuthenticationFilter rememberMeAuthenticationFilter() throws Exception {
    RememberMeAuthenticationFilter myFilter = new RememberMeAuthenticationFilter(authenticationManagerBean(), tokenBasedRememberMeServices());
    return myFilter;
}

```

然后尴尬来了,remember是好使了,但是退出不清除cookie了,最后只找到TokenBasedRememberMeServices的logoutHandler没有被加到logoutFilter里(参考),至于为什么(或者哪里配置的不对)还不清楚,有知道的可以告知下,暂时还是使用之前的那种配置方式吧。

二、持久化 Token方式

待更新

Spring Security4实例(Java config 版) —— Remember-Me的更多相关文章

  1. Spring Security4实例(Java config版)——ajax登录,自定义验证

    本文源码请看这里 相关文章: Spring Security4实例(Java config 版) -- Remember-Me 首先添加起步依赖(如果不是springboot项目,自行切换为Sprin ...

  2. Spring MVC 的 Java Config ( 非 XML ) 配置方式

    索引: 开源Spring解决方案--lm.solution 参看代码 GitHub: solution/pom.xml web/pom.xml web.xml WebInitializer.java ...

  3. spring+mybati java config配置引起的bean相互引用日志报警告问题

    摘要: Error creating bean with name 'XXX': Requested bean is currently in creation: Is there an unreso ...

  4. 纯java config配置Spring MVC实例

    1.首先创建一个Maven工程,项目结构如下: pom.xml添加Spring和servlet依赖,配置如下 <project xmlns="http://maven.apache.o ...

  5. Spring MVC4 + Spring Security4 + Hibernate实例

    http://www.yiibai.com/spring-security/spring-mvc-4-and-spring-security-4-integration-example.html 在这 ...

  6. spring、springmvc和mybatis整合(java config方式)

    之前项目中使用ssm框架大多是基于xml的方式,spring3.0以后就提供java config的模式来构建项目,并且也推荐使用这种方式,自从接触过springboot后,深深感受到这种纯java配 ...

  7. spring java config 初探

    Java Config 注解 spring java config作为同xml配置形式的另一种表达形式,使用的场景越来越多,在新版本的spring boot中 大量使用,今天我们来看下用到的主要注解有 ...

  8. Spring 4 and MyBatis Java Config

    TL;DR With the Java Config enhancements in Spring 4, you no longer need xml to configure MyBatis for ...

  9. Spring知识点回顾(01)Java Config

    Spring知识点回顾(01) 一.Java Config 1.服务和服务注入 2.Java 注解 :功能更强一些 3.测试验证 二.注解注入 1.服务和服务注入 2.配置加载 3.测试验证 三.总结 ...

随机推荐

  1. 【T-SQL】系列文章全文目录(2017-06-02更新)

    本系列[T-SQL]主要是针对T-SQL的总结. T-SQL基础 [T-SQL基础]01.单表查询-几道sql查询题 [T-SQL基础]02.联接查询 [T-SQL基础]03.子查询 [T-SQL基础 ...

  2. 几个页面loading样式

    随手练习了几个loading样式,以后看到有意思的loading样式也会补充上.样式的兼容性建议还是去w3c上看下属性的兼容性,至少我习惯这么多,当然,w3c中文网貌似很久很久没更新过了,可能更好的还 ...

  3. 将数据的初始化放到docker中的整个工作过程(问题记录)

    将数据的初始化放到docker中的整个工作过程 由于是打算作为个人博客,所以对于install这个步骤,我从一开始就打算删掉的,前面一个多星期一直在修bug,到前天才开始做这个事情. 过程中也是碰到了 ...

  4. 线程(java课堂笔记)

    1.两种方式的差异 2.线程的生命周期 3.线程控制(线程的方法) 4.线程同步 5.线程同步锁 一. 两种方式的差异 A extends Thread :简单 不能再继承其他类了(Java单继承)同 ...

  5. 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(下篇)

    Configuration的newStatementHandler分析 SimpleExecutor的doUpdate方法上文有分析过: public int doUpdate(MappedState ...

  6. MVC4加载zTree树小控件

    前言: 第一次学习使用MVC框架,找了个练手项目,加载zTree树小控件.下面我就一步步说明我这次练手的经历以记录.如果有什么错误,希望各位大神帮忙指正,谢谢. 第一步: 利用VS2010新建一个MV ...

  7. 【PHP】最详细PHP从入门到精通(三)——PHP中的数组常用函数汇总

     PHP从入门到精通 之PHP中的数组常用函数详解 数组作为PHP中最常用的结构之一,PHP强大的数组函数功能,给数组的相关操作带来了极大的便利.今天给大家介绍的PHP中数组函数,是PHP数组中重要的 ...

  8. Vue按需加载提升用户体验

    Vue官方文档异步组件: 在大型应用中,我们可能需要将应用拆分为多个小模块,按需从服务器下载.为了让事情更简单, Vue.js 允许将组件定义为一个工厂函数,动态地解析组件的定义.Vue.js 只在组 ...

  9. UE4 Run On owing Client解析(RPC测试)

    今天看到文档中游戏性指南->远程调用函数->在蓝图中使用远程调用函数的 Run On Owning Client 在所有权的客户端上运行部分,发现把Add Item和Remove Item ...

  10. 【源码分享】mui实现简单的手机音乐播放器

    mui实现简单的手机音乐播放器 最近先来无事,我用mui写了一个可以跨页面控制的音乐播放器.主要功能有上一曲,下一曲,播放,暂停,感兴趣的可以继续看下去. 说的总是不实在,直接上源码,有兴趣的可以读下 ...