`
Vertonur
  • 浏览: 7010 次
  • 性别: Icon_minigender_1
  • 来自: 广州
文章分类
社区版块
存档分类
最新评论

是谁动了我的cookie?Spring Security自动登录功能开发经历总结

阅读更多

在使用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 cookie认证

    Spring Security 是一个强大的安全框架,用于Java和Spring应用程序。它为Web应用提供了全面的安全解决...在实际开发中,结合文件"springsecurity"中的示例代码,可以更好地理解和实践Spring Security的Cookie认证功能。

    Spring Security 3多用户登录实现之九 基于持久化存储的自动登录

    在“Spring Security 3多用户登录实现之九 基于持久化存储的自动登录”这一主题中,我们将探讨如何利用Spring Security来实现多用户登录系统,并通过持久化存储来支持用户的自动登录功能。在这个过程中,我们会涉及...

    Spring Security2中设置Cookie的保存时间

    这里,`&lt;remember-me&gt;`元素用于启用自动登录功能,即“记住我”。`key`属性定义了一个密钥,用于加密存储在Cookie中的数据。`token-validity-seconds`属性则指定了Cookie的有效期,以秒为单位。例如,如果设置为`...

    SpringSecurity退出功能实现的正确方式(推荐)

    Spring Security 退出功能实现的正确方式 Spring Security 框架提供了强大的安全功能,其中退出功能是非常重要的一部分。本文将介绍如何正确地实现 Spring Security 退出功能。 一、logout 最简及最佳实践 使用 ...

    spring security 完整项目实例

    在这个完整的项目实例中,我们将深入探讨Spring Security的核心概念以及如何将其应用于实际的Web应用程序开发。 首先,我们从用户、角色和权限这三个核心元素开始。在Spring Security中,用户信息通常存储在一个...

    Spring Security 资料合集

    - 在Spring Boot项目中,Spring Security 可以通过自动配置快速启动,开发者只需少量配置就能实现复杂的安全需求。 这三份资料——"实战Spring Security 3.x.pdf"、"Spring Security 3.pdf" 和 "Spring Security...

    Spring Boot Security 2.5.8 实现账号、手机号、邮件登录,记住密码等功能

    总之,这个项目提供了全面的示例,展示如何利用Spring Boot Security 2.5.8实现多种登录方式,并结合记住密码和JWT功能,构建了一个健壮的安全认证系统。对于任何想要在Spring Boot应用中实施安全控制的开发者来说,...

    SpringSecurity实现表单安全登录、图形验证的校验、记住我时长控制机制、第三方登录

    本主题将深入探讨如何使用SpringSecurity实现表单安全登录、图形验证码的验证、记住我功能的时长控制以及整合第三方登录。 **表单安全登录** 在SpringSecurity中,我们可以方便地配置表单登录。首先,我们需要定义...

    Spring security实现记住我下次自动登录功能过程详解

    Spring Security 实现记住我下次自动登录功能过程详解 Spring Security 是一个功能强大且广泛使用的 Java 认证和授权框架,提供了许多功能强大的功能,例如认证、授权、RememberMe 等。在本文中,我们将详细介绍...

    SpringSecurity入门小demo(SSM+Spring Security)

    - **记住我功能**:允许用户在一段时间内免密登录,通常使用 Cookie 实现。 - **CSRF 保护**:防止跨站请求伪造攻击,Spring Security 默认开启。 通过这个小 demo,你可以对 Spring Security 有初步的认识,了解...

    Spring Security3.1实践

    - **Remember Me服务**:实现自动登录功能,通过设置cookie存储用户的登录状态。 - **CSRF防护**:防止跨站请求伪造攻击,Spring Security 3.1默认开启CSRF保护。 - **国际化支持**:Spring Security支持多语言...

    SpringSecurity实战代码

    本实战代码将带你深入理解并实际操作SpringSecurity的核心功能,包括用户认证与授权、注销、权限限制、"记住我"功能以及首页定制。 1. 用户认证与授权:在SpringSecurity中,认证过程是识别用户身份,而授权则是...

    SpringSecurity+MVC入门Demo

    通过这个"SpringSecurity+MVC入门Demo",初学者可以了解如何在Spring MVC应用中集成Spring Security,实现用户认证和授权的基本流程,为后续深入学习和实践打下基础。这个Demo应该包含了配置文件、控制器、视图和...

    springsecurity3.0.5应用

    4. **Remember Me**:Spring Security允许配置“记住我”功能,这样用户在关闭浏览器后再次访问时无需重新登录。这通过在用户成功登录时创建一个长期有效的cookie实现。 5. **Session Management**:Spring ...

    SpringBoot集成Spring Security入门程序并实现自动登录【完整源码+数据库】

    在这个入门程序中,我们将深入理解如何将SpringBoot与Spring Security整合,以实现用户身份验证和授权功能,并通过自动登录功能提升用户体验。 首先,Spring Security的核心功能包括身份验证(Authentication)和...

    spring security 数据库存储资源信息 记住我 支持AJAX

    在提供的压缩包文件"springsecurity_database"中,可能包含了示例代码、配置文件和其他相关资源,可以帮助你理解和实现上述功能。在实际项目中,你需要根据自己的需求调整和扩展这些示例,以构建一个符合业务场景的...

    Spring Security3.2搭建LDAP认证授权

    在Spring Security 3.2版本中,我们可以利用其功能来构建基于 Lightweight Directory Access Protocol (LDAP) 的认证和授权系统,同时结合"Remember-me"功能,实现用户登录后的持久化会话。 **一、LDAP简介** LDAP...

    spring-security 案例

    - **RememberMeServices**:提供“记住我”功能,让用户在关闭浏览器后仍能自动登录。 5. **自定义登录逻辑** - **自定义AuthenticationProvider**:如果需要使用非默认的验证逻辑,可以创建自己的...

    Spring Security学习总结

    Spring Security的Remember Me服务通过在用户登录时创建一个长期的凭据,并将其存储在用户的Cookie中,以便在后续的无状态请求中自动重新认证用户。 7. **CSRF防护**:为了防止跨站请求伪造,Spring Security提供了...

    SpringSecurity

    这个jar文件包含了SpringSecurity框架的核心类和库,使得开发人员能够方便地在项目中引入并配置安全功能。 通过学习和使用SpringSecurity,开发者能够构建出安全可靠的应用,同时避免在安全方面投入过多的时间和...

Global site tag (gtag.js) - Google Analytics