8000 Spring Security4实例(Java config 版) —— Remember-Me · Issue #15 · x113773/testall · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Spring Security4实例(Java config 版) —— Remember-Me #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
x113773 opened this issue Jul 12, 2017 · 3 comments
Open

Spring Security4实例(Java config 版) —— Remember-Me #15

x113773 opened this issue Jul 12, 2017 · 3 comments
Labels

Comments

@x113773
Copy link
Owner
x113773 commented Jul 12, 2017

本文源码请看这里

相关文章:
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>
  1. 配置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(比如为了加个验证码),上面的配置应该就可以了。否则还得继续配置:


  1. 设置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")还是要设置的。

  1. 在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功能总是有点不安全的。

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:

<bean id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />
</bean>

<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
</bean>

<bean id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>
</bean>

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配置开始修改):


  1. 注释掉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();
	}
  1. 上面的3、4步还是需要的,此外还要加上之前没有配置的rememberMeAuthenticationProvider和rememberMeAuthenticationFilter,下面就直接把相关代码都贴上了
@Bean
	public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exce
8000
ption {
		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方式

待更新

@x113773 x113773 changed the title Spring Security4实例 —— Remember-Me Spring Security4实例(Java config 版) —— Remember-Me Jul 13, 2017
@x113773
Copy link
Owner Author
x113773 commented Jul 14, 2017

后经网友提醒,在退出时手动删除cookie,暂时先这么解决吧

.logout().deleteCookies("remember-me")

@fanslin
Copy link
fanslin commented Apr 17, 2018

有没有xml配置版本?

@x113773
Copy link
Owner Author
x113773 commented Apr 17, 2018

@fanslin 抱歉,没有哦

@x113773 x113773 added doc and removed doc labels Sep 28, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants
0