phpgt / csrf
自动防御跨站请求伪造。
Requires
- php: >=8.1
- phpgt/dom: ^v4.0
- phpgt/session: ^v1.1
Requires (Dev)
- phpstan/phpstan: ^v1.8
- phpunit/phpunit: ^v9.5
This package is auto-updated.
Last update: 2024-09-09 20:08:05 UTC
README
此库自动为您处理 CSRF 保护,包括生成令牌,将其注入页面中的所有表单,并在收到 POST 请求时验证是否存在有效的令牌。
使用方法:三步保护
CSRF 库做两件事
- 将 CSRF 令牌注入到
form
中 - 验证
POST
请求以确保它们包含有效的令牌
每一步都只需要一个方法调用,但您需要先进行设置。
第一步:设置
首先创建 TokenStore。目前有两种实现——ArrayTokenStore
和 SessionTokenStore
。《ArrayTokenStore》是最基本的,不会以任何方式持久化,但可以扩展到自定义集成。《SessionTokenStore》是一个内置实现,可以在请求之间持久化令牌,以便生成的令牌可以在另一个请求上检查。添加 CSRF 保护的最简单方法是使用会话。
use Gt\Csrf\SessionTokenStore; // $session is an object-oriented representation of $_SESSION // that implements the Gt\Session\SessionContainer Interface. $tokenStore = new SessionTokenStore($session);
第二步:验证
在运行任何其他代码(特别是可能影响数据的东西)之前,您应该检查是否需要有效的 CSRF 令牌。
use Gt\Csrf\Exception\CSRFException; if(this_is_a_post_request()) { try { $tokenStore->verify(); } catch(CSRFException $e) { // Stop processing this request and get out of there! } }
如果请求包含 POST 而没有有效的 CSRF 令牌,将抛出 CSRFException
——因此您应该计划捕获它。记住,如果发生这种情况,请求是欺诈性的,因此您不应该处理它!
第三步:为下一次注入
最后,一旦您处理完 HTML 代码并准备好将其发送回客户端,您应该注入 CSRF 令牌。如果您不这样做,当页面提交时,请求将无法通过第二步!
use Gt\Csrf\HTMLDocumentProtector; // The html can come in as anything accepted by Gt\Dom\HTMLDocument - here it's a // plain string in a variable. $html = "<html>...</html>"; // Now do the processing. $protector = new HTMLDocumentProtector($html, $tokenStore); $protector->protect(); // Output the HTML of the document - you will see the new fields have // been automatically injected. echo $protector->getHTMLDocument();
使用不同长度的令牌
默认情况下,生成 32 个字符的令牌。它们使用字符集 [a-zA-Z0-9],这意味着一个 64 位的令牌,一个每秒发出 100,000 个请求的暴力攻击者需要大约 2.93 亿年才能猜出来。如果这看起来过多或不足,您可以使用 TokenStore::setTokenLength()
改变令牌长度。
关于客户端请求的特别说明
请注意,如果您的页面上有多个表单,将为每个表单生成一个唯一的令牌并将其注入。当使用客户端请求(XMLHTTPRequest 或 Fetch,即 AJAX)提交表单时,响应将包含一个新的令牌,该令牌必须在页面中刷新以供下一次提交使用。
如果您希望每个页面只有一个令牌,跨所有表单共享,可以通过将 TOKEN_PER_PAGE 参数传递给 projectAndInject 方法进行配置:$page->protectAndInject(HTMLDocumentProtector::TOKEN_PER_PAGE);
。
每个页面存储一个令牌将减少服务器资源的需求,但并发客户端请求将失败,这就是为什么默认情况下每个表单只有一个令牌的原因。
存储令牌在会话中的替代方案
此包包含一个 ArrayTokenStore
,可以存储在会话中。您可以通过扩展 TokenStore
并实现抽象方法来实现替代令牌存储,例如 RDBMS 或 NoSQL。