谷歌浏览器80版本SameSite属性所引发的一系列问题



前言

写博客主要是为了记录一下工作中所遇到的一些问题,下一次再出现相同的问题,也好迅速的解决,本篇文章用来记录登录模块所遇到的一些坑。


一、场景分析

某天,前端小姐姐突然问我,为啥她的验证码一直过期(线上环境),经过一顿排查,发现是浏览器的问题,只有用谷歌浏览器(80版本之后)才会出现验证码失效问题。经过一顿搜索找到答案,谷歌浏览器在80版本之后,对SameSite属性做出了一些更改,它的默认值从None变为了Lax,桥豆麻袋!!!SameSite是什么玩意,我咋没听过?别急。现在就给你讲解讲解

Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击和用户追踪。
它有三个属性。分别是Strict,Lax,None

  1. Strict:完全禁止第三方获取cookie,跨站点时,任何情况下都不会发送cookie;
  2. Lax:大多数情况下禁止获取cookie,除非导航到目标网址的GET请求(链接、预加载、GET表单);
  3. None:没有限制。

在谷歌的80版本发布之后,对SameSite属性做了以下更改:

  1. 没有设置SameSite属性的默认SameSite=Lax;
  2. SameSite=None的cookie则必须设置为Secure,即安全链接(https)。

那这又跟验证码失效有什么关系呢,我的验证码存在redis中,key是用户的sessionId,发现每次请求验证码时,请求头中携带的sessionId都是不一样的,那么登录时校验验证码的key,肯定就不一样了,自然就会校验失败。知道了原因那么这个问题就差不多解决了百分之80

二、解决方案

1.浏览器解决方案

直接设置谷歌浏览器的属性,改变SameSite的默认配置即可,
在谷歌浏览器中输入chrome://flags/,然后按照下图进行配置,重新launch即可。
在这里插入图片描述

2.代码解决方案

在网上找了很多这里贴出几个帖子,反正我自己试了没有成功,或许是我太菜
1.关于解决Chrome新版本中cookie跨域携带和samesite的问题处理
2.方法二的话就用JWT,来实现无状态服务,下一篇博客再详细说明。
3.在请求验证码时,就将sessionId通过响应头的方式返回,前端在登录时,添加一个请求头,然后后端进行处理
代码如下

//创建验证码
public void create(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        String key = session.getId();
        ValidateCodeProperties code = properties.getCode();
        response.setHeader("sid",key);
        response.setHeader("Access-Control-Expose-Headers", "sid");
        setHeader(response, code.getType());
        Captcha captcha = createCaptcha(code);
        redisService.set(FebsConstant.CODE_PREFIX  + key, StringUtils.lowerCase(captcha.text()), code.getTime());
        captcha.out(response.getOutputStream());
    }

自定义一个SessionManager,让它继承DefaultSessionManager,具体看代码

@Configuration
public class MySessionManager extends DefaultWebSessionManager {

    private static final String TOKEN = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(TOKEN);
        //如果请求头中有 token 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

然后将自定义的sessionManager交给Shiro

 @Bean
    public SecurityManager securityManager(ShiroRealm shiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 配置 SecurityManager,并注入 shiroRealm
        securityManager.setRealm(shiroRealm);
        // 配置 shiro session管理器
        securityManager.setSessionManager(sessionManager());
        // 配置 缓存管理类 cacheManager
        securityManager.setCacheManager(cacheManager());
        // 配置 rememberMeCookie
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }
	@Bean
    public DefaultWebSessionManager sessionManager() {

        MySessionManager mySessionManager = new MySessionManager();
        Collection<SessionListener> listeners = new ArrayList<>();
        listeners.add(new ShiroSessionListener());
        // 设置 session超时时间
        mySessionManager.setGlobalSessionTimeout(febsProperties.getShiro().getSessionTimeout() * 1000L);
        mySessionManager.setSessionListeners(listeners);
        mySessionManager.setSessionDAO(redisSessionDAO());
        mySessionManager.setSessionIdUrlRewritingEnabled(false);

        return mySessionManager;
    }

总结

大致思路就这几个吧,如果改浏览器会显得很业余,但总感觉session存也有点不妥,找个时间弄一个无状态的Token解决方案。
Token的解决方案已更新 JWT 的一站式解决方案

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值