【问题标题】:Browser Cookie never expires浏览器 Cookie 永不过期
【发布时间】:2022-11-15 00:54:41
【问题描述】:

我第一次实施使用 HttpOnly Cookie 登录身份验证.就我而言,它是在用户调用 login 方法时创建的Python 服务使用 fastapi 和 uvicorn。

我已经阅读了 MDN 文档来实现 expires 属性,因此,浏览器会在时间到期时删除此 cookie。

我已经使用 http.cookies 和 Morsel 在 Python 中实现了 Cookie 以应用HttpOnly像这样的财产:

from http import cookies
from fastapi import FastAPI, Response, Cookie, Request
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

mytoken = 'blablabla'

def getUtcDate():
    sessionDate = datetime.now()
    sessionDate += timedelta(minutes=2)
    return sessionDate.strftime('%a, %d %b %Y %H:%M:%S GMT')

@app.get('cookietest')
def getCookie(response: Response):
   cookie = cookies.Morsel()
   cookie['httponly'] = True
   cookie['version'] = '1.0.0'
   cookie['domain'] = '127.0.0.1'
   cookie['path'] = '/'
   cookie['expires'] = getUtcDate()
   cookie['max-age'] = 120
   cookie['samesite'] = 'Strict'
   cookie.set("jwt", "jwt", mytoken)

   response.headers.append("Set-Cookie", cookie.output())

   return {'status':'ok'}

这样做,当我调用“cookietest”端点时,Cookies 在浏览器中看起来正确,证据是:

如图所示,cookie 在 Expires / Max-Age 中有一个过期日期时间:“Wed, 12 Oct 2022 11:24:58 GMT”,登录后 2 分钟(如果用户在 14:05 登录: 00,cookie 在 14:07:00 到期)

我的问题是超过过期时间时,任何浏览器都不会删除 cookie,所以这让我很困惑。如果我等了几分钟,然后向另一个端点(如http://127.0.0.1:8000/info)发出请求,cookie 仍然存在于 http 标头中。

问题是什么?我做错了什么?我正在阅读很多关于 cookie 存储和过期的文档,但我看不到任何关于这个问题的信息。

非常感谢问候

已编辑:问题已解决

正如Chris 所说,使用 FastApi 中的 set_cookie 方法解决了问题。

我仍然想知道为什么 MSD 文档表明日期格式必须是特定的格式,不会导致浏览器删除 Cookie,但指示以秒为单位的时间可以正常工作。

@app.get("/cookietest")
async def cookietest(response: Response):
    response.set_cookie(
        key='jwt', 
        value=getToken(), 
        max_age=120, 
        expires=120, 
        path='/', 
        secure=False, 
        httponly=True, 
        samesite="strict", 
        domain='127.0.0.1'
    )
    return {"Result": "Ok"}

【问题讨论】:

  • 只是让您知道您可以使用 Response 对象的 set_cookie 方法创建 cookie,如 this answer 中所述。另请参阅相关的FastAPI documentationStarlette documentation
  • 您可以在set_cookie 方法中设置expires 标志,该方法采用一个整数来定义cookie 过期前的秒数。例如,如果您希望 cookie 在创建后 2 分钟内过期,请使用 expires=120
  • 好的,我使用 fastapi 中的 set_cookie 方法更改了我的代码,现在可以使用了,但是,为什么 MDN 文档说 Expires 使用格式如下的 DateTime “Expires: Wed, 21 Oct 2015 07:28:00 GMT”?

标签: python-3.x cookies fastapi uvicorn cookie-httponly


【解决方案1】:

使用expires 标志时,日期必须与您当前使用的格式以及GMT(格林威治标准时间)时区完全相同。您的 cookie 在创建 2 分钟后没有过期的原因是因为您使用的是 datetime.now(),它返回当前当地的日期和时间。

因此,例如,如果您当前的本地时区是 GMT+2 并且时间是 20:30:00(因此,GMT 时间是 18:30:00),创建一个在 20:32:00 GMT 过期的 cookie 实际上会告诉浏览器删除它cookie 在 2 小时 2 分钟内(从它创建的时间开始)。如果您在浏览器的 DevTools 中查看 cookie 的 Expires / Max-Age 列(例如,在 Chrome 上,转到 DevTools 中的 Network 选项卡,单击请求的名称,然后单击 Cookies 选项卡),您会注意到一个 @ 987654345@ 在日期时间的末尾,这意味着 UTC(协调世界时)——即与 UTC 的偏移量为零时分秒。您还可以检查响应标头,您可以在其中看到 cookie 的 expires 标志设置为 20:32:00 GMT。没有明显的time difference between UTC and GMT(如果您想了解更多关于它们的差异,请查看此post)。

因此,您可以在代码中将 .now() 替换为 .utcnow()

from datetime import timedelta, datetime

def get_expiry():
    expiry = datetime.utcnow()
    expiry += timedelta(seconds=120)
    return expiry.strftime('%a, %d-%b-%Y %T GMT')

或使用time.gmtime(),作为secs 参数传递time.time()(以秒为单位返回时间)加上所需的租用时间(以秒为单位):

import time

def get_expiry():
    lease = 120  # seconds
    end = time.gmtime(time.time() + lease)
    return time.strftime('%a, %d-%b-%Y %T GMT', end)

对于上述两种方法,请使用:

cookie['expires'] = get_expiry()

您也可以使用未记录的方式直接以秒为单位传递到期时间。例如:

cookie['expires'] = 120

expires 的替代方法是 max-age 标志,它指定 cookie 从当前时刻起以秒为单位的过期时间(类似于上述方式)。如果设置为零或负值,cookie 将被立即删除。例子:

cookie['max-age'] = 120

笔记:

如果同时设置了expiresmax-age,则max-age 具有优先权(参见相关documentation on MDN)。

另外,根据RFC 6265

4.1.2.1。 Expires 属性

Expires 属性表示 cookie 的最大生命周期, 表示为日期和时间cookie 过期的时间。用户 代理无需在指定日期之前保留 cookie 已通过。事实上,用户代理经常因为内存而驱逐 cookie 压力或隐私问题。

4.1.2.2。 Max-Age 属性

Max-Age 属性表示 cookie 的最大生命周期, 表示为数直到 cookie 过期。这 用户代理不需要保留指定的 cookie 期间。事实上,用户代理经常因为内存而驱逐 cookie 压力或隐私问题。

NOTE: Some existing user agents do not support the Max-Age 
attribute. User agents that do not support the Max-Age attribute 
ignore the attribute.

如果 cookie 有两个都Max-AgeExpires 属性, Max-Age 属性具有优先级并控制过期日期 饼干。如果 cookie 有两者都不Max-AgeExpires 属性,用户代理将保留 cookie 直到“当前 会话结束”(由用户代理定义)。

另请注意,如MDN documentation 中提到的关于expires 标志:

警告: 许多网络浏览器都有会话恢复将保存所有选项卡并在下次使用浏览器时恢复它们的功能。 会话 cookies 也将被恢复,就好像浏览器从未被 关闭。

另一件需要注意的是,自 2022 年 9 月以来,Chrome limits the cookie's max-age to 400 days

当使用显式 Expires/Max-Age 属性设置 cookie 时,现在将限制为不超过 400 天在将来。 以前,没有限制,cookie 最多可以过期 未来几千年。

使用 FastAPI/Starlette

还应该注意的是,FastAPI/Starlette 提供了一种更简单的方法来在Response 对象上设置cookie,使用set_cookie 方法,如this answer 中所述。根据Starlette documentation

  • max_age - 一个整数定义 cookie 的生命周期.负整数或0的值将被丢弃 饼干立即。 Optional
  • expires - 一个整数定义的数量直到 cookie 过期。 Optional

来自FastAPI documentation 的示例:

from fastapi import FastAPI, Response

app = FastAPI()

@app.post('/')
def create_cookie(response: Response):
    response.set_cookie(key='fakesession', value='fake-value', max_age=120, expires=120)
    return {'message': 'success'}

【讨论】:

  • 信息量很大,很有用。感谢您的解释和时间。
猜你喜欢
  • 1970-01-01
  • 2015-07-30
  • 2013-07-18
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 2021-12-20
  • 2015-08-12
  • 2011-04-21
相关资源
最近更新 更多