DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)

文章目录

      • 前文
      • 排查
      • 总结

前文

  最近碰到DJango登录系统出现问题,而这问题实在是难以定位,网上搜了大量的资料皆找不到解决方法,后面借助google才逐渐解决了问题,特此记录下。问题是:极小部分同事用chrom无法登录上系统,而大部分同事(包括我)却又能正常登陆,然后让不能登录的同事换个浏览器后就恢复正常了。而这种偶发+偏偏存在的问题最是让人苦恼,这种我就完全没办法调试…所幸后面还是发现并解决了问题。
  跳过排查的,可以直接看总结部分答案。

排查

  系统是基于DJango+element-ui这样的前后端分离框架,而根据调试能看到login是登录成功,在获取用户信息的时候报错,此时根据下图可以看到用户是Anonymous(匿名用户),这是在未登录Django的时候才会出现的,而login模块如下:
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第1张图片

def login(request):
    data = json.loads(request.body.decode("utf-8"))
    username = data["username"]
    password = data["password"]

    user = authenticate(username=username, password=password)
    if user and user.is_active:
        auth_login(request, user)
        response = HttpResponse(json.dumps({"sessionid": request.session.session_key}))
        response['Access-Control-Allow-Methods'] = 'GET,PUT,POST,DELETE,PATCH,OPTIONS'
        return response
    else:
        return JsonResponse({
            "errno": -1,
            "errmsg": "验证失败"
        }, status=403)

 根据login模块,可以确定当login成功返回浏览器,那必然已经经过authenticate和auth_login,并且再登录redis后确认session的确已录到系统,于是排除是auth_login、authenticate这类Django的类的异常,回到前端控制台,查看在登录成功后前端为了获取用户信息向后端发送请求时未带上用户的sessionid,于是后端系统才报错Anonymous。至此锁定问题点为,前端未获取到对应的session。
  那就有两种可能:

  1. login返回给前端时未保存session,导致前端获取不到
  2. login返回时前端正常保存session到浏览器,但是在请求时没去取session

  查看login,可以发现成功返回时set-cookie返回了csrf token和sessionid,然后再点击控制台的application查看是否存储,此时可以发现并没有存储(下面的第二张图只是展示查看cookie的位置,并不是实际的图片)。所以可以锁定是第一种情况。
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第2张图片
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第3张图片
  排查到是cookie未能保存,此时一个问题就出现了,既然是未能保存,为啥大部分浏览器都没问题,而只有极少部分的才行呢?那肯定不是前端代码的问题,于是打开前端文件确认的确不是:
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第4张图片
  所以前端正常保存,而我的能正常登陆查看sessionid也的确老老实实地待在localstorage里面。这时想到了我们是跨域的访问,即使做了cors处理可能还是有问题,详细查看如图:
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第5张图片  那关于跨域,在Django里是引入了django-cors-headers来处理的(相关操作可以上网查询),在element-ui里是直接设置了withCredentials: true,而正常来说,这两者就足够跨域转发了,实际上也稳定跑了好久,直到今年疫情的出现。。也就是2月份后开始陆陆续续收到用户登录不了的信息。而到此时也终于让我发现了问题所在,登录不了的chrom浏览器有提示这么一段话:

The set-cookie didn’t specify a “SameSite” attritube…

  我一脸懵逼,并没有设置过SameSite这个玩意,上网搜了下的确是为了防止csrf跨站攻击而出的,可以查看这篇文章说明:https://www.cnblogs.com/ziyunfei/p/5637945.html。具体有三种模式,分别是strict、lax、None,依次是严格、宽松、禁用。具体如下:
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第6张图片
  那因为我有csrf token防范,而且是内部系统,完全没必要严格,只要设置成None禁用就行了,那此时就有个问题了,谁帮我设置了?很明显,是chrom,然后google到了chrom于2020-02-04上了强制性的samesite(chrom 80版本),不过应该只是少部分灰度吧,毕竟绝大多数人的还是能正常使用。具体可以查看如下几篇文章了解:

  • Google Chrome SameSite Cookie 策略
  • Chrome 80 跨域 Cookie 变化的影响和应对方案
    然后就是这篇文章,如何解决这个问题,提出了几点:
  • https://help.salesforce.com/articleView?id=000351874&language=zh_CN&mode=1&type=1
    DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第7张图片
      然后回到Django,不就是设置下header就行了吗,所以我先直接这样操作:
response["Set-Cookie"]="SameSite=None;Secure"

  然后发现无效,此时才知道这一行需要和set-cookie那一行在同一行,而DJango的set-cookie在session里是由中间件session处理的,除非改源代码,不然无法操作,此时只能去翻官网找解决方案:官网介绍。
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第8张图片
  还真的有方案,不过是Django 2.1版本才提供,而我的才是1.11版本,那不支持,也暂时不想升级,此时翻到了替代方案:官网替代方案,即通过安装一个中间件来处理,详细如下:
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第9张图片
  然后根据操作设置成lax,结果还是不行,即使宽松也仍是触发了,所以只能设置成None,结果直接无效了,原来这套操作不支持None,查看github的issue是有提出相关解决方案:issue。
DJango碰到samesite无法登录Chrom(解决DJango无法获取Chrom的cookie问题)_第10张图片
  根据上面的操作设置成”none“结果还是无效(提示必须有secure这个属性才行)。这也怪作者,在quickstart的时候并没有指示如何设置none,后面在issue发现了如何解决,即这样设置:

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'None'

  毕竟当前是非常偶发的,如何简单处理呢?直接让chrom禁用这个samesite功能即可。打开链接:chrome://flags/#same-site-by-default-cookies。然后搜索:SameSite by default cookies,将default改成disable即可:
在这里插入图片描述

总结

  根据上述,总共三种操作方式,汇整如下:

  • 最简单:设置chrom为禁用samesite
  • DJango版本低于2.1:引入库django-cookie-samesite来处理
  • Django版本高于2.1:直接设置SESSION_COOKIE_SAMESITE=None

你可能感兴趣的:(Django)