在使用Spring Security的remember me模块为云端开源论坛XLineCode开发自动登录模块时,本着用户至上的理念优先采用了PersistentTokenBasedRememberMeServices作为实现根基。通过参考Spring Security的官网推荐书籍Spring Security 3 ,很简单也很顺利的在Spring Security命名空间下配置了该service,本地环境也简单测试通过了该功能,但在上线后接近两个月的时间内,却出现了各种多线程引发的问题而导致自动登录功能失效的情况。
Spring Security的remember me模块的两个实现机制简介:
PersistentTokenBasedRememberMeServices:
使用JdbcTokenRepositoryImpl在数据库创建persistent_logins表。当勾选自动登录时随机产生键值对并将该键值对保存在persistent_logins表及浏览器的cookie中,在浏览器当前会话失效后从下一request中提取cookie键值对,通过对比是否与数据库中的键值对一致来判断该自动功能是否有效。有效则根据该key生成新的token并更新至数据库和浏览器。如根据key未能从数据库找到记录,则不进行登录授权并取消该key在浏览器中对应的cookie。如该key对应的cookie token与数据库得到的token不一致,则该cookie在浏览器中可能已经被黑客攻破,被盗用于信息窃取,因此抛出cookie被盗的异常并根据该用户id删除其在数据库对应的所有键值记录(该用户在多个浏览器登录则有多条记录)。
核心方法processAutoLoginCookie代码如下:
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { if (cookieTokens.length != 2) { throw new InvalidCookieException("Cookie token did not contain " + 2 + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'"); } final String presentedSeries = cookieTokens[0]; final String presentedToken = cookieTokens[1]; PersistentRememberMeToken token = tokenRepository.getTokenForSeries(presentedSeries); if (token == null) { // No series match, so we can't authenticate using this cookie throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries); } // We have a match for this user/series combination if (!presentedToken.equals(token.getTokenValue())) { // Token doesn't match series value. Delete all logins for this user and throw an exception to warn them. tokenRepository.removeUserTokens(token.getUsername()); throw new CookieTheftException(messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.")); } if (token.getDate().getTime() + getTokenValiditySeconds()*1000L < System.currentTimeMillis()) { throw new RememberMeAuthenticationException("Remember-me login has expired"); } // Token also matches, so login is valid. Update the token value, keeping the *same* series number. if (logger.isDebugEnabled()) { logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'"); } PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), generateTokenData(), new Date()); try { tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate()); addCookie(newToken, request, response); } catch (DataAccessException e) { logger.error("Failed to update token: ", e); throw new RememberMeAuthenticationException("Autologin failed due to data access problem"); } return getUserDetailsService().loadUserByUsername(token.getUsername()); }
TokenBasedRememberMeServices:
根据用户id和密码生成cookie,同样通过对比判断cookie是否有效。该机制的缺点是用户修改密码后其在其他浏览器中的cookie不会自动更新,需再次登陆生成cookie。另一缺点是密码经过加密后保存在客户端,存在一定风险。
核心方法processAutoLoginCookie代码如下:
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { if (cookieTokens.length != 3) { throw new InvalidCookieException("Cookie token did not contain 3" + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'"); } long tokenExpiryTime; try { tokenExpiryTime = new Long(cookieTokens[1]).longValue(); } catch (NumberFormatException nfe) { throw new InvalidCookieException("Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1] + "')"); } if (isTokenExpired(tokenExpiryTime)) { throw new InvalidCookieException("Cookie token[1] has expired (expired on '" + new Date(tokenExpiryTime) + "'; current time is '" + new Date() + "')"); } // Check the user exists. // Defer lookup until after expiry time checked, to possibly avoid expensive database call. UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]); // Check signature of token matches remaining details. // Must do this after user lookup, as we need the DAO-derived password. // If efficiency was a major issue, just add in a UserCache implementation, // but recall that this method is usually only called once per HttpSession - if the token is valid, // it will cause SecurityContextHolder population, whilst if invalid, will cause the cookie to be cancelled. String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(), userDetails.getPassword()); if (!equals(expectedTokenSignature,cookieTokens[2])) { throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2] + "' but expected '" + expectedTokenSignature + "'"); } return userDetails; }
使用PersistentTokenBasedRememberMeServices遇到的各种情况:
1. 因Spring Security的filter链未作并发控制,所以PersistentTokenBasedRememberMeServices的processAutoLoginCookie在处理自动登录时如服务器负载过大用户多次刷新页面会产生线程A和B同时在运行processAutoLoginCookie的情况。假设A是先于B的请求,而A在未执行tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());时被系统挂起了线程,导致B先于A完成了请求。此时A获得资源后执行完时因A请求已被浏览器取消,故A所产生的新的cookie无法在浏览器中更新,导致该浏览器的下次请求中的cookie与数据库中的token不一致从而抛出Cookie被盗的异常(该情况只在极低的概率下才可能发生)。
2. 跑完PersistentTokenBasedRememberMeServices的processAutoLoginCookie后在业务逻辑中如抛出异常而未被代码或者web.xml中的500配置捕获,则会直接返回500错误给浏览器,此时新生成的cookie不会在浏览器更新,同样会导致cookie被盗的情况。不过一般情况下都会捕获,问题不大。
3. filter配置不当导致拦截了页面加载后触发的ajax或一般业务请求:同上,在发生异常时导致cookie更新失败。
4. 在浏览器请求未返回时直接取消该请求,因该请求在服务器执行完后不能正常设置浏览器的cookie,导致浏览器下次请求再次使用失效的cookie时processAutoLoginCookie发现两边token不一致,抛出cookie被盗异常。这种情况在网络不理想的情况下被用户触发的可能性最大,而Spring Security代码中也没有处理该情况的代码。仅仅因为用户取消请求而抛出cookie被盗的异常并清空该用户在其他浏览器的cookie会造成相当差的用户体验,这也是我决定转为采用TokenBasedRememberMeServices的原因.
使用TokenBasedRememberMeServices则相对简单很多,无需考虑并发的情况,只要cookie匹配就可自动登录。
后话:我在发现该情况后分别尝试了oschina,csdn和iteye的登录机制,发现他们均使用的是TokenBasedRememberMeServices的方式。
相关推荐
Spring Security 是一个强大的安全框架,用于Java和Spring应用程序。它为Web应用提供了全面的安全解决...在实际开发中,结合文件"springsecurity"中的示例代码,可以更好地理解和实践Spring Security的Cookie认证功能。
在“Spring Security 3多用户登录实现之九 基于持久化存储的自动登录”这一主题中,我们将探讨如何利用Spring Security来实现多用户登录系统,并通过持久化存储来支持用户的自动登录功能。在这个过程中,我们会涉及...
这里,`<remember-me>`元素用于启用自动登录功能,即“记住我”。`key`属性定义了一个密钥,用于加密存储在Cookie中的数据。`token-validity-seconds`属性则指定了Cookie的有效期,以秒为单位。例如,如果设置为`...
Spring Security 退出功能实现的正确方式 Spring Security 框架提供了强大的安全功能,其中退出功能是非常重要的一部分。本文将介绍如何正确地实现 Spring Security 退出功能。 一、logout 最简及最佳实践 使用 ...
在这个完整的项目实例中,我们将深入探讨Spring Security的核心概念以及如何将其应用于实际的Web应用程序开发。 首先,我们从用户、角色和权限这三个核心元素开始。在Spring Security中,用户信息通常存储在一个...
- 在Spring Boot项目中,Spring Security 可以通过自动配置快速启动,开发者只需少量配置就能实现复杂的安全需求。 这三份资料——"实战Spring Security 3.x.pdf"、"Spring Security 3.pdf" 和 "Spring Security...
总之,这个项目提供了全面的示例,展示如何利用Spring Boot Security 2.5.8实现多种登录方式,并结合记住密码和JWT功能,构建了一个健壮的安全认证系统。对于任何想要在Spring Boot应用中实施安全控制的开发者来说,...
本主题将深入探讨如何使用SpringSecurity实现表单安全登录、图形验证码的验证、记住我功能的时长控制以及整合第三方登录。 **表单安全登录** 在SpringSecurity中,我们可以方便地配置表单登录。首先,我们需要定义...
Spring Security 实现记住我下次自动登录功能过程详解 Spring Security 是一个功能强大且广泛使用的 Java 认证和授权框架,提供了许多功能强大的功能,例如认证、授权、RememberMe 等。在本文中,我们将详细介绍...
- **记住我功能**:允许用户在一段时间内免密登录,通常使用 Cookie 实现。 - **CSRF 保护**:防止跨站请求伪造攻击,Spring Security 默认开启。 通过这个小 demo,你可以对 Spring Security 有初步的认识,了解...
- **Remember Me服务**:实现自动登录功能,通过设置cookie存储用户的登录状态。 - **CSRF防护**:防止跨站请求伪造攻击,Spring Security 3.1默认开启CSRF保护。 - **国际化支持**:Spring Security支持多语言...
本实战代码将带你深入理解并实际操作SpringSecurity的核心功能,包括用户认证与授权、注销、权限限制、"记住我"功能以及首页定制。 1. 用户认证与授权:在SpringSecurity中,认证过程是识别用户身份,而授权则是...
通过这个"SpringSecurity+MVC入门Demo",初学者可以了解如何在Spring MVC应用中集成Spring Security,实现用户认证和授权的基本流程,为后续深入学习和实践打下基础。这个Demo应该包含了配置文件、控制器、视图和...
4. **Remember Me**:Spring Security允许配置“记住我”功能,这样用户在关闭浏览器后再次访问时无需重新登录。这通过在用户成功登录时创建一个长期有效的cookie实现。 5. **Session Management**:Spring ...
在这个入门程序中,我们将深入理解如何将SpringBoot与Spring Security整合,以实现用户身份验证和授权功能,并通过自动登录功能提升用户体验。 首先,Spring Security的核心功能包括身份验证(Authentication)和...
在提供的压缩包文件"springsecurity_database"中,可能包含了示例代码、配置文件和其他相关资源,可以帮助你理解和实现上述功能。在实际项目中,你需要根据自己的需求调整和扩展这些示例,以构建一个符合业务场景的...
在Spring Security 3.2版本中,我们可以利用其功能来构建基于 Lightweight Directory Access Protocol (LDAP) 的认证和授权系统,同时结合"Remember-me"功能,实现用户登录后的持久化会话。 **一、LDAP简介** LDAP...
- **RememberMeServices**:提供“记住我”功能,让用户在关闭浏览器后仍能自动登录。 5. **自定义登录逻辑** - **自定义AuthenticationProvider**:如果需要使用非默认的验证逻辑,可以创建自己的...
Spring Security的Remember Me服务通过在用户登录时创建一个长期的凭据,并将其存储在用户的Cookie中,以便在后续的无状态请求中自动重新认证用户。 7. **CSRF防护**:为了防止跨站请求伪造,Spring Security提供了...
这个jar文件包含了SpringSecurity框架的核心类和库,使得开发人员能够方便地在项目中引入并配置安全功能。 通过学习和使用SpringSecurity,开发者能够构建出安全可靠的应用,同时避免在安全方面投入过多的时间和...