mober / rememberme
安全的“记住我”功能
Requires
- php: 8.0 - 8.3
- spomky-labs/base64url: ^2.0
- symfony/polyfill-php82: ^1.29
Requires (Dev)
- ircmaxell/random-lib: ^1.1
- phpunit/phpunit: ^9.5
- squizlabs/php_codesniffer: ^3.5
- vimeo/psalm: ^4.29
Suggests
- ext-pdo: *
- ircmaxell/random-lib: For generating random tokens with different entropy sources
- predis/predis: Needed if you used Redis storage
README
此库实现了在网站上实现安全“记住我”功能的最佳实践。登录信息和唯一的安全令牌存储在cookie中。如果用户访问网站,cookie中的登录信息将与服务器上存储的信息进行比较。如果令牌匹配,则用户将被登录。用户可以在多个计算机/浏览器上拥有登录cookie。
升级到版本 5.x
从v5.0.0开始,此库使用declare(strict_types=1);
,仅与PHP 8.0及以上版本兼容,并带有更改后的默认值
- 使用SHA-2(256)而不是SHA-1
- 令牌使用URL安全的base64编码(而不是十六进制)
- 令牌分隔符现在是URL安全的(
credential
现在可能包含令牌分隔符) - 令牌长度从16字节增加到32字节
虽然可以配置哈希算法、令牌长度和令牌编码,但分隔符不可配置。新的默认值提高了安全性,同时减少了cookie的大小。遗憾的是,这使得v5.0.0+与旧版本不兼容。您应该清除令牌存储,因为所有旧令牌都将被检测为无效。
新选项:配置令牌轮换
Authenticator类有一个新的setter setTokenRotationEnabled()
(默认:true
)。如名称所示,这将启用或禁用令牌轮换。这是由于令牌轮换问题而实现的
- 并发请求将提交相同的Triplet,但只有一个将被接受。其他请求将触发操作检测,因为它具有正确的永久令牌但无效的一次性令牌。
- 如果页面加载被用户取消或由于网络问题,浏览器可能无法接收到新的Triplet。在下一次访问时,它尝试提交旧的cookie,再次具有正确的永久但无效的一次性令牌,并触发操作检测。
这可能导致用户似乎在所有设备上“随机注销”。使用此选项,您可以决定是否希望获得最大的安全性或更好的用户体验。请注意,如果(太多)假阳性,用户也可能开始忽略“您的cookie被盗”警告。
安装
对于PHP 8.0或更高版本,将安装版本5+。对于PHP 7.4及更早版本,将安装不再维护的版本4。
composer require mober/rememberme
使用示例
请参阅example
目录中的示例。您可以使用以下命令在本地机器上运行它:
php -S 127.0.0.1:8085 -t example
要了解基本的应用程序结构,请查看index.php
和user_is_looged_in.php
模板。
该示例使用文件系统在服务器端存储令牌。在大多数情况下,最好将存储与PDOStorage
类交换。
数据库配置
为了使用数据库作为存储,您需要提供PDOStorage
的实例
$storage = new PDOStorage([ 'TableName' => 'tokens', 'CredentialColumn' => 'credential', 'TokenColumn' => 'token', 'PersistentTokenColumn' => 'persistent_token', 'ExpiresColumn' => 'expires', 'Connection' => /* supply your instance of PDO here */, ]); $auth = new Authenticator(storage: $storage);
请参阅resources/sql
目录,了解创建模式示例。
cookie配置
默认情况下,cookie的有效期为一周,且适用于设置它的域名下的所有路径。它无法通过JavaScript访问/修改,并将通过HTTP连接传输。如果您的应用程序需要不同的配置(例如,如果您正在使用HTTPS并希望通过仅允许通过安全连接传输cookie来增强安全性),您可以创建自己的PHPCookie实例
$expire = strtotime('1 week', 0); $cookie = new PHPCookie( name: 'REMEMBERME', expireTime: $expire, path: '/', domain: 'example.org', secure: true, httpOnly: true, sameSite: 'Lax', ); $auth = new Authenticator(cookie: $cookie); $auth->setExpireTime($cookie->getExpireTime());
令牌安全性
此库默认使用random_bytes
函数生成32字节的令牌。这对大多数应用程序来说应该足够安全。
如果您需要更高的安全性,请使用自定义令牌生成器实例化Authenticator
类。以下示例生成64字节Base64编码的令牌
$tokenGenerator = new DefaultToken(64, DefaultToken::FORMAT_BASE64); $auth = new Authenticator($storage, $tokenGenerator);
如果您想对随机令牌的生成有更多控制,请查看RandomLib。Rememberme有一个可以使用的RandomLibToken
类。
清理过期令牌
从您的存储(文件系统或数据库)中清理过期令牌的最佳方法是编写一个小脚本,该脚本初始化您的令牌存储类并调用其cleanExpiredTokens
方法。定期使用cron作业或其他工作方法运行此脚本。
如果您不能定期运行清理脚本,并且网站流量较低,您可以在每次页面调用时通过以下方式初始化Authenticator类来清理存储
$auth = new Authenticator($storage); $auth->setCleanExpiredTokensOnLogin(true);
工作原理
此库受到了Barry Jaspan的文章《Improved Persistent Login Cookie Best Practice》的极大启发。该库可以防止以下攻击场景
- 用户的计算机被盗或被破坏,攻击者可以使用现有的"Remember Me" cookie登录。用户知道这种情况发生了。用户可以远程使所有登录cookie失效。
- 攻击者已经获取了"Remember Me" cookie并使用它登录。用户不知道这一点。下次他尝试使用被盗的cookie登录时,他会收到警告,并且所有登录cookie都会失效。
- 攻击者已从服务器获取登录令牌数据库。存储的令牌被哈希处理,因此他必须付出计算努力(彩虹表或暴力破解)才能使用它们。
- 攻击者尝试通过系统性地生成"Remember Me" cookie来进行暴力破解登录。默认安全设置下,每秒100次尝试(一个非常高的数字,可能会出现在服务器日志中),需要8个月的时间才能有50%的机会猜对一个cookie的值。