用于防范CSRF攻击的ASGI中间件
项目描述
asgi-csrf
用于防范CSRF攻击的ASGI中间件
安装
pip install asgi-csrf
背景
请参阅OWASP关于跨站请求伪造(CSRF)的指南以及他们的跨站请求伪造(CSRF)预防技巧表。
此中间件实现了双重提交cookie模式,其中设置一个cookie,然后将其与隐藏的csrftoken
表单字段或x-csrftoken
HTTP头进行比较。
用法
像这样装饰您的ASGI应用程序
from asgi_csrf import asgi_csrf
from .my_asgi_app import app
app = asgi_csrf(app, signing_secret="secret-goes-here")
如果缺少cookie,中间件将设置一个csrftoken
cookie。该令牌的值将通过scope["csrftoken"]
函数提供给您的ASGI应用程序。
您的应用程序代码应将此值作为隐藏表单字段包含在任何POST表单中
<form action="/login" method="POST">
...
<input type="hidden" name="csrftoken" value="{{ request.scope.csrftoken() }}">
</form>
请注意,request.scope["csrftoken"]()
是一个函数,返回一个字符串。调用该函数也使中间件知道如果用户尚未具有该cookie,该页面应该设置cookie。
如果需要设置cookie,中间件将在响应中添加一个Vary: Cookie
头,以确保它不会被任何CDN或中间代理错误地缓存。
对于任何不包括匹配的csrftoken
(无论是POST数据还是x-csrftoken
HTTP头)的POST请求,中间件将返回403禁止错误(对于JavaScript fetch()
调用非常有用)。
signing_secret
用于签名令牌,以防止子域漏洞。
如果您没有传递显式的signing_secret
参数,中间件将查找ASGI_CSRF_SECRET
环境变量。
如果找不到该环境变量,它将生成一个随机密钥,该密钥将存在于服务器的整个生命周期中。
这意味着如果您未配置特定密钥,您用户的csrftoken
cookie将在服务器每次重启时都变为无效!您应该配置一个密钥。
如果尚未设置,则始终设置cookie
默认情况下,此中间件仅在用户遇到需要它的页面时才设置csrftoken
cookie - 例如,该页面调用request.scope["csrftoken"]()
函数,以便在表单中填充隐藏字段。
如果您希望中间件为任何尚未提供cookie的传入请求设置该cookie,则可以使用always_set_cookie=True
参数
app = asgi_csrf(app, signing_secret="secret-goes-here", always_set_cookie=True)
配置cookie
中间件可以通过多个选项配置来控制如何设置CSRFcookie
app = asgi_csrf(
app,
signing_secret="secret-goes-here",
cookie_name="csrftoken",
cookie_path="/",
cookie_domain=None,
cookie_secure=False,
cookie_samesite="Lax"
)
cookie_name
:要设置的cookie名称。默认为"csrftoken"
。cookie_path
:cookie有效的路径。默认为"/"
,表示cookie对整个域名有效。cookie_domain
:cookie有效的域名。默认为None
,表示cookie仅对当前域名有效。cookie_secure
:如果设置为True
,则cookie仅通过HTTPS连接发送。默认为False
。cookie_samesite
:控制cookie如何与跨站请求一起发送。可以是"Strict"
、"Lax"
或"None"
。默认为"Lax"
。
跳过CSRF保护的其它情况
如果请求包含Authorization: Bearer ...
标题,通常用于OAuth和JWT身份验证,则请求不需要包含CSRF令牌。这是因为浏览器无法在可能被滥用的上下文中发送这些标题。
如果请求没有任何cookie,则允许其通过,因为CSRF保护仅适用于来自已认证用户的请求。
always_protect
如果您有应该始终受保护且没有cookie的路径 - 例如您的登录表单(以避免登录CSRF攻击) - 您可以通过传递它们作为always_protect
参数来保护这些路径
app = asgi_csrf(
app,
signing_secret="secret-goes-here",
always_protect={"/login"}
)
skip_if_scope
可能存在您想要为认证POST请求禁用CSRF保护的情况 - 例如,对于Web API。
skip_if_scope=
参数可以用于提供回调函数,该函数传递ASGI作用域并返回对于该请求是否跳过CSRF保护的True
。
此示例跳过了任何传入请求的CSRF保护,其中请求路径以/api/
开头
def skip_api_paths(scope)
return scope["path"].startswith("/api/")
app = asgi_csrf(
app,
signing_secret="secret-goes-here",
skip_if_scope=skip_api_paths
)
使用send_csrf_failed自定义错误
默认情况下,当CSRF令牌缺失或无效时,中间件将返回一个包含简短错误消息的403禁止访问页面。
您可以通过传递一个send_csrf_failed
函数到中间件来自定义此行为。此函数应接受ASGI的scope
和send
函数,以及发生错误的message_id
。
message_id
将是一个表示asgi_csrf.Errors
枚举中项的整数。
此示例展示了如何根据该message_id
自定义错误消息
async def custom_csrf_failed(scope, send, message_id):
assert scope["type"] == "http"
await send(
{
"type": "http.response.start",
"status": 403,
"headers": [[b"content-type", b"text/html; charset=utf-8"]],
}
)
await send(
{
"type": "http.response.body",
"body": {
Errors.FORM_URLENCODED_MISMATCH: "custom form-urlencoded error",
Errors.MULTIPART_MISMATCH: "custom multipart error",
Errors.FILE_BEFORE_TOKEN: "custom file before token error",
Errors.UNKNOWN_CONTENT_TYPE: "custom unknown content type error",
}
.get(message_id, "")
.encode("utf-8"),
}
)
app = asgi_csrf(
app,
signing_secret="secret-goes-here",
send_csrf_failed=custom_csrf_failed
)
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源分发
构建分发
asgi_csrf-0.10.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 77e160e20a4477fe3363a316176ec56ba816a5d1e004cca63020e3031ab2ff6e |
|
MD5 | 19849cb28767900e26e2eb06cb794f2b |
|
BLAKE2b-256 | a2e864cc146fc63b32fdecdee52aea2f10f519ea5029d33e8090f397cbd25fb1 |
asgi_csrf-0.10-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | cf2f0464f7a5342dad0656dd78e670741461ed6ee584391d28a0a4b61ac6daaf |
|
MD5 | fa1635c31f7fca8882d961d04b985bd4 |
|
BLAKE2b-256 | 47eecd939c6a6143059f06b3babb2655af1308922940b429d66fc70885b9bb3a |