当前位置: 首页 > news >正文

springsecurity 源码解读 之 RememberMeAuthenticationFilter

RememberMeAuthenticationFilter 的作用很简单,就是用于当session 过期后,系统自动通过读取cookie 让系统自动登录。

我们来看看Springsecurity的过滤器链条。

我们发现这个 RememberMeAuthenticationFilter  在 匿名构造器之前,这个是为什么呢?

还是从源码来分析:

if (SecurityContextHolder.getContext().getAuthentication() == null) {
            Authentication rememberMeAuth = rememberMeServices.autoLogin(request, response);

            if (rememberMeAuth != null) {

代码中有这样的一行,当SecurityContext 中 Authentication 为空时,他就会调用 rememberMeServices 自动登录。

 

因此刚刚的问题也就好解释了,因为如果RememberMeAuthenticationFilter  没有实现自动登录,那么他的Authentication 还是为空,

这是可以创建一个匿名登录,如果先创建了匿名登录,那么这个 RememberMeAuthenticationFilter   的判断就不会为null,过滤器将失效。

 

我们看看rememberMeServices 的autoLogin 登录

我们贴出实现的代码:

public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
        String rememberMeCookie = extractRememberMeCookie(request);

        if (rememberMeCookie == null) {
            return null;
        }

        logger.debug("Remember-me cookie detected");

        if (rememberMeCookie.length() == 0) {
            logger.debug("Cookie was empty");
            cancelCookie(request, response);
            return null;
        }

        UserDetails user = null;

        try {
            String[] cookieTokens = decodeCookie(rememberMeCookie);
            user = processAutoLoginCookie(cookieTokens, request, response);
            userDetailsChecker.check(user);

            logger.debug("Remember-me cookie accepted");

            return createSuccessfulAuthentication(request, user);
        } catch (CookieTheftException cte) {
            cancelCookie(request, response);
            throw cte;
        } catch (UsernameNotFoundException noUser) {
            logger.debug("Remember-me login was valid but corresponding user not found.", noUser);
        } catch (InvalidCookieException invalidCookie) {
            logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());
        } catch (AccountStatusException statusInvalid) {
            logger.debug("Invalid UserDetails: " + statusInvalid.getMessage());
        } catch (RememberMeAuthenticationException e) {
            logger.debug(e.getMessage());
        }

        cancelCookie(request, response);
        return null;
    }
extractRememberMeCookie这个方法判断 SPRING_SECURITY_REMEMBER_ME_COOKIE 这样的cookie,如果没有就直接返回了null。

processAutoLoginCookie:这个是处理cookie 并从cookie加载用户。

默认springsecurity 使用类 TokenBasedRememberMeServices 来解析 cookie。

实现代码如下:
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;
    }

  
 protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
        String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("No MD5 algorithm available!");
        }

        return new String(Hex.encode(digest.digest(data.getBytes())));
    }

 

这个代码就是加载用户,并验证cookie 是否有效。

因此我们在写入cookie 时,产生的cookie 过程如下:

String enPassword=EncryptUtil.hexToBase64(password);
        
        long tokenValiditySeconds = 1209600; // 14 days
        long tokenExpiryTime = System.currentTimeMillis() + (tokenValiditySeconds * 1000);
        String signatureValue = makeTokenSignature(tokenExpiryTime,username,enPassword);
        String tokenValue = username + ":" + tokenExpiryTime + ":" + signatureValue;
        String tokenValueBase64 = new String(Base64.encodeBase64(tokenValue.getBytes()));
                
        CookieUtil.addCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 
                tokenValueBase64,60 * 60 * 24 * 365, true, request, response);

 

 


 

转载于:https://www.cnblogs.com/yg_zhang/p/10659490.html

相关文章:

  • 算法:四舍六入五成双 ,保留三位有效数字
  • VS 编码规范---- 代码注释设置
  • Tuxedo入门学�
  • mybatis的collection查询问题以及使用原生解决方案的结果
  • javaweb_CSS
  • sam哥其实是个安静的美男子...
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • 经典面试题1
  • JAVA学习路线 零基础新手必备
  • Flask+jinja2 开发Puppet用户和节点管理系统
  • 痞子衡嵌入式:如果i.MX RT是一匹悍马,征服它时别忘了用马镫MCUBootUtility
  • 武汉大学2014年基础数学复试试题参考解答
  • chown: changing ownership of `.': Invalid argument
  • 面试题:数组原型上实现一个去重的方法
  • ×××案例之一路由之间的×××
  • 【翻译】babel对TC39装饰器草案的实现
  • Angular6错误 Service: No provider for Renderer2
  • dva中组件的懒加载
  • es6
  • Flannel解读
  • Go 语言编译器的 //go: 详解
  • HTML中设置input等文本框为不可操作
  • javascript 哈希表
  • Java多线程(4):使用线程池执行定时任务
  • magento 货币换算
  • Mithril.js 入门介绍
  • PhantomJS 安装
  • SpiderData 2019年2月13日 DApp数据排行榜
  • vue脚手架vue-cli
  • windows下如何用phpstorm同步测试服务器
  • 阿里云应用高可用服务公测发布
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 前端代码风格自动化系列(二)之Commitlint
  • 少走弯路,给Java 1~5 年程序员的建议
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 译自由幺半群
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • 在weex里面使用chart图表
  • 整理一些计算机基础知识!
  • #LLM入门|Prompt#3.3_存储_Memory
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (2)Java 简介
  • (done) NLP “bag-of-words“ 方法 (带有二元分类和多元分类两个例子)词袋模型、BoW
  • (windows2012共享文件夹和防火墙设置
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)基于IDEA的JAVA基础10
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .