goedemiddag / webauthn-fork
使用设备、指纹或生物识别数据验证用户。再见,密码!
Requires
- php: >=7.4.0
- ext-json: *
- illuminate/support: ^8.0 || ^9.0 || ^10.0
- nyholm/psr7: ^1.3
- ramsey/uuid: ^4.0
- spomky-labs/base64url: ^2
- symfony/psr-http-message-bridge: ^2.0
- thecodingmachine/safe: ^1.3.3
- web-auth/webauthn-lib: ^4.7
Requires (Dev)
- laravel/framework: ^10
- laravel/tinker: ^2.8
- phpunit/phpunit: ^9.5.2
This package is auto-updated.
Last update: 2024-09-09 14:40:09 UTC
README
Larapass
使用设备、指纹或生物识别数据验证用户。再见,密码!
这使您可以在 Laravel 身份验证驱动程序内部启用 WebAuthn 身份验证,并包含 除了厨房用具之外的所有功能。
要求
- PHP 7.4 或 PHP 8.0
- Laravel 7.18(2020 年 7 月)或 Laravel 8.x
对于 Laravel 9.x 及更高版本的支持,请使用 Laragear WebAuthn。
安装
只需在控制台中使用 Composer 即可。
composer require darkghosthunter/larapass
不幸的是,使用 WebAuthn 并不是“轻而易举”,此包允许您以最 简单的方式 启用 WebAuthn。
目录
什么是 WebAuthn?它是如何使用指纹或其他方法的?
简而言之,主要浏览器与 Web 身份验证 API 兼容,将身份验证推向设备(指纹、面部 ID、图案、代码等),而不是纯文本密码。
此包使用自定义 用户提供程序 从设备验证 WebAuthn 有效负载。
如果您对 WebAuthn 有任何疑问,请查看此简短常见问题解答。对于更深入的了解,请查阅 WebAuthn.io、WebAuthn.me 和 Google WebAuthn 教程。
设置
我们需要确保您的用户可以注册其设备并使用它们进行验证。
之后,您可以使用包含的控制器和辅助程序快速开始 WebAuthn,使您的生活更加轻松。
1. 添加 eloquent-webauthn
驱动程序
此包包含一个与 Eloquent 兼容的 用户提供程序,用于验证来自设备的 WebAuthn 响应。
转到您的 config/auth.php
配置文件,并将您使用的提供程序的驱动程序更改为 eloquent-webauthn
。
return [ // ... 'providers' => [ 'users' => [ // 'driver' => 'eloquent', // Default Eloquent User Provider 'driver' => 'eloquent-webauthn', 'model' => App\User::class, ], ] ];
如果您计划为 WebAuthn 创建自己的用户提供程序驱动程序,请记住注入
WebAuthnAssertValidator
以正确验证用户和传入的响应。
2. 创建 webauthn_credentials
表
通过发布迁移文件并迁移表来创建 webauthn_credentials
表
php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="migrations"
php artisan migrate
3. 实现合约和特质
将 WebAuthnAuthenticatable
合约和 WebAuthnAuthentication
特质添加到 Authenticatable
用户类,或任何使用认证的类。
<?php namespace App; use Illuminate\Foundation\Auth\User as Authenticatable; use DarkGhostHunter\Larapass\Contracts\WebAuthnAuthenticatable; use DarkGhostHunter\Larapass\WebAuthnAuthentication; class User extends Authenticatable implements WebAuthnAuthenticatable { use WebAuthnAuthentication; // ... }
该特质用于将用户模型与数据库中包含的 WebAuthn 数据关联起来。
4. 注册路由(可选)
最后,您需要添加注册和认证用户的路由。如果您想快速入门,只需发布 Larapass 中包含的控制器。
php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="controllers"
您可以将这些路由定义复制粘贴到您的 routes/web.php
文件中。
use App\Http\Controllers\Auth\WebAuthnRegisterController; use App\Http\Controllers\Auth\WebAuthnLoginController; Route::post('webauthn/register/options', [WebAuthnRegisterController::class, 'options']) ->name('webauthn.register.options'); Route::post('webauthn/register', [WebAuthnRegisterController::class, 'register']) ->name('webauthn.register'); Route::post('webauthn/login/options', [WebAuthnLoginController::class, 'options']) ->name('webauthn.login.options'); Route::post('webauthn/login', [WebAuthnLoginController::class, 'login']) ->name('webauthn.login');
在您的前端脚本中,将这些请求指向这些路由。
如果您想完全控制,可以选择不使用这些辅助控制器,并使用自己的逻辑。如果您需要从某处开始,可以使用
AttestWebAuthn
和AssertsWebAuthn
特质。
5. 使用 JavaScript 辅助工具(可选)
此软件包包含一个方便的脚本,用于通过 WebAuthn 处理注册和登录。要使用它,只需将 larapass.js
资产发布到您的应用程序公共资源中。
php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="public"
您将收到 vendor/larapass/js/larapass.js
文件,您可以将其包含到您的认证视图中,并按需使用它。
<script src="{{ asset('vendor/larapass/js/larapass.js') }}"></script> <!-- Registering credentials --> <script> const register = (event) => { event.preventDefault() new Larapass({ register: 'webauthn/register', registerOptions: 'webauthn/register/options' }).register() .then(response => alert('Registration successful!')) .catch(response => alert('Something went wrong, try again!')) } document.getElementById('register-form').addEventListener('submit', register) </script> <!-- Login users --> <script> const login = (event) => { event.preventDefault() new Larapass({ login: 'webauthn/login', loginOptions: 'webauthn/login/options' }).login({ email: document.getElementById('email').value }).then(response => alert('Authentication successful!')) .catch(error => alert('Something went wrong, try again!')) } document.getElementById('login-form').addEventListener('submit', login) </script>
如果您使用默认值,可以省略路由列表声明。上面的示例只是为了展示。请确保根据您的需求修改此脚本。
此外,辅助工具允许在注册和登录的动作请求中添加头信息。
new Larapass({ login: 'webauthn/login', loginOptions: 'webauthn/login/options' }).login({ email: document.getElementById('email').value, }, { 'My-Custom-Header': 'This is sent with the signed challenge', })
您可以将它复制粘贴并导入到 Laravel Mix、Babel 或 Webpack 这样的转译器中。如果脚本不符合您的需求,您也可以创建自己的脚本。
记住用户
您可以通过在从前端推送签名的登录挑战时将 WebAuthn-Remember
头值设置为 true
来启用它。我们可以使用 包含的 JavaScript 辅助工具 轻松完成此操作。
new Larapass.login({ email: document.getElementById('email').value }, { 'WebAuthn-Remember': true })
或者,如果您使用自己的脚本,可以在出发的 JSON 负载数组中添加 remember
键。两种方式都可行。
您可以在
AssertsWebAuthn
特质中覆盖此行为。
6. 设置账户恢复(可选)
您可能想提供一个“恢复”账户的方法,以防用户丢失其凭据,这基本上是附加一个新的凭据。您可以使用这些控制器,这些控制器也已发布,以及这些路由
use App\Http\Controllers\Auth\WebAuthnDeviceLostController; use App\Http\Controllers\Auth\WebAuthnRecoveryController; Route::get('webauthn/lost', [WebAuthnDeviceLostController::class, 'showDeviceLostForm']) ->name('webauthn.lost.form'); Route::post('webauthn/lost', [WebAuthnDeviceLostController::class, 'sendRecoveryEmail']) ->name('webauthn.lost.send'); Route::get('webauthn/recover', [WebAuthnRecoveryController::class, 'showResetForm']) ->name('webauthn.recover.form'); Route::post('webauthn/recover/options', [WebAuthnRecoveryController::class, 'options']) ->name('webauthn.recover.options'); Route::post('webauthn/recover/register', [WebAuthnRecoveryController::class, 'recover']) ->name('webauthn.recover');
这些控制器附带 新视图 和 翻译行,因此如果您对包含的内容不满意,可以覆盖它们。
您还可以在 resources/vendor/larapass
中覆盖视图,并使用用户的 sendCredentialRecoveryNotification
方法发送通知。
之后,不要忘记在您的 config/auth.php
中添加一个新的令牌经纪人。我们需要它来存储恢复过程中的令牌。
return [ // ... 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], // New for WebAuthn 'webauthn' => [ 'provider' => 'users', // The user provider using WebAuthn. 'table' => 'web_authn_recoveries', // The table to store the recoveries. 'expire' => 60, 'throttle' => 60, ], ], ];
确认中间件
遵循与 password.confirm
中间件 相同的原则,Larapass 包含了一个 webauthn.confirm
中间件,该中间件会在用户进入特定路由之前,要求用户使用其设备进行确认。
Route::get('this/is/important', function () { return 'This is very important!'; })->middleware('webauthn.confirm');
在 发布控制器 时,WebAuthnConfirmController
将会在你的控制器文件中就绪,等待接收确认。你只需要通过复制粘贴以下内容来注册路由。
Route::get('webauthn/confirm', 'Auth\WebAuthnConfirmController@showConfirmForm') ->name('webauthn.confirm.form'); Route::post('webauthn/confirm/options', 'Auth\WebAuthnConfirmController@options') ->name('webauthn.confirm.options'); Route::post('webauthn/confirm', 'Auth\WebAuthnConfirmController@confirm') ->name('webauthn.confirm');
与以往一样,你可以根据自己的逻辑选择退出。对于这种情况,请查看 ConfirmsWebAuthn
特性 以开始。
事件
由于所有认证都由 Laravel 本身处理,因此包含的唯一事件是 事件,即 AttestationSuccessful
,它在注册成功时触发。它包含持久化的凭据用户。
你可以使用此事件来执行操作,例如通知用户已注册新设备。为此,你可以使用一个 监听器。
public function handle(AttestationSuccessful $event) { $event->user->notify( new DeviceRegisteredNotification($event->credential->getId()) ); }
使用 WebAuthn 的操作
此包简化了操作 WebAuthn 的 仪式(认证和断言)。为此,请使用方便的 WebAuthn
门面。
认证(注册)
使用 generateAttestation
和 validateAttestation
为你的用户进行操作。后者返回验证过的凭据,因此你可以手动保存它们。
<?php use App\User; use Illuminate\Support\Facades\Auth; use DarkGhostHunter\Larapass\Facades\WebAuthn; $user = Auth::user(); // Create an attestation for a given user. return WebAuthn::generateAttestation($user);
然后我们可以在以后验证它
<?php use App\User; use Illuminate\Support\Facades\Auth; use DarkGhostHunter\Larapass\Facades\WebAuthn; $user = Auth::user(); // Verify it $credential = WebAuthn::validateAttestation( request()->json()->all(), $user ); // And save it. if ($credential) { $user->addCredential($credential); } else { return 'Something went wrong with your device!'; }
断言(登录)
对于断言,只需使用 generateAssertion
创建请求,并使用 validateAssertion
进行验证。
<?php use App\User; use DarkGhostHunter\Larapass\Facades\WebAuthn; // Find the user to assert, if there is any $user = User::where('email', request()->input('email'))->first(); // Create an assertion for the given user (or a blank one if not found); return WebAuthn::generateAssertion($user);
然后我们可以在以后验证它
<?php use App\User; use Illuminate\Support\Facades\Auth; use DarkGhostHunter\Larapass\Facades\WebAuthn; // Verify the incoming assertion. $credentials = WebAuthn::validateAssertion( request()->json()->all() ); // If is valid, login the user of the credentials. if ($credentials) { Auth::login( User::getFromCredentialId($credentials->getPublicKeyCredentialId()) ); }
凭据
你可以通过用户实例直接管理用户凭据,这得益于 WebAuthnAuthenticatable
接口。最有用的方法是
hasCredential()
:检查用户是否有特定的凭据 ID。addCredential()
:添加新的凭据来源。removeCredential()
:通过其 ID 移除现有的凭据。flushCredentials()
:删除所有凭据。你可以通过其 ID 排除凭据。enableCredential()
:将现有的凭据 ID 包含在认证中。disableCredential()
:从认证中排除现有的凭据 ID。getFromCredentialId()
:如果存在,则返回使用给定凭据 ID 的用户。
你可以使用这些方法,例如,将被盗设备/凭据列入黑名单并注册新的,或者通过清除所有已注册设备来完全禁用 WebAuthn。
高级配置
Larapass 旨在即插即用,但你可以通过发布配置文件来覆盖配置。
php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="config"
之后,你将收到包含此数组的 config/larapass.php
配置文件
<?php return [ 'relaying_party' => [ 'name' => env('WEBAUTHN_NAME', env('APP_NAME')), 'id' => env('WEBAUTHN_ID'), 'icon' => env('WEBAUTHN_ICON'), ], 'bytes' => 16, 'timeout' => 60, 'cache' => env('WEBAUTHN_CACHE'), 'algorithms' => [ \Cose\Algorithm\Signature\ECDSA\ES256::class, \Cose\Algorithm\Signature\EdDSA\Ed25519::class, \Cose\Algorithm\Signature\ECDSA\ES384::class, \Cose\Algorithm\Signature\ECDSA\ES512::class, \Cose\Algorithm\Signature\RSA\RS256::class, ], 'attachment' => null, 'conveyance' => 'none', 'login_verify' => 'preferred', 'userless' => null, 'unique' => false, 'fallback' => true, 'confirm_timeout' => 10800, ];
转接方信息
return [ 'relaying_party' => [ 'name' => env('WEBAUTHN_NAME', env('APP_NAME')), 'id' => env('WEBAUTHN_ID'), 'icon' => env('WEBAUTHN_ICON'), ], ];
转接方 只是一种在用户设备中唯一标识你的应用的方式
name
:应用的名称。默认为应用名称。id
:应用的可选域名。如果为 null,设备将内部填充。icon
:可选的 BASE64(最大 128 字节)图像数据或图像 URL。
考虑使用基础域名如
myapp.com
作为id
,以允许所有子域名(如foo.myapp.com
)上的凭据。
挑战配置
return [ 'bytes' => 16, 'timeout' => 60, 'cache' => env('WEBAUTHN_CACHE'), ];
待签名的出站挑战是一个随机字节数组。这控制了字节数量、挑战的超时时间(之后被标记为无效),以及设备在解决挑战时使用的缓存。
算法
return [ 'algorithms' => [ \Cose\Algorithm\Signature\ECDSA\ES256::class, // ECDSA with SHA-256 \Cose\Algorithm\Signature\EdDSA\Ed25519::class, // EdDSA \Cose\Algorithm\Signature\ECDSA\ES384::class, // ECDSA with SHA-384 \Cose\Algorithm\Signature\ECDSA\ES512::class, // ECDSA with SHA-512 \Cose\Algorithm\Signature\RSA\RS256::class, // RSASSA-PKCS1-v1_5 with SHA-256 ], ];
这控制了认证器(设备)如何操作来创建公钥和私钥。这些COSE算法是最适用于设备内和漫游密钥的,因为某些算法必须在低带宽协议上传输。
除非你知道自己在做什么,否则请添加或删除类。真的。只需保持它们原样。
密钥附加
return [ 'attachment' => null, ];
默认情况下,用户决定使用什么进行注册。如果您希望仅使用跨平台认证(如USB密钥、CA服务器或证书),请将其设置为true
,或者如果您想强制执行仅设备认证,请设置为false
。
证言传递
return [ 'conveyance' => null, ];
认证传递表示您是否需要验证设备密钥。虽然大多数时候不需要,但您可以将此更改为indirect
(您验证它来自可信来源)或direct
(设备包含验证数据)。
如果您不知道自己在做什么,请保持原样。
登录验证
return [ 'login_verify' => 'preferred', ];
默认情况下,大多数认证器在登录时会要求用户验证。您可以覆盖此设置并将其设置为required
,如果您想没有例外。
您还可以使用discouraged
来仅检查用户存在(如“继续”按钮),这可能使登录更快,但会稍微降低安全性。
当将无用户设置为
preferred
或required
时,将自动覆盖为required
。
无用户登录(一键,无类型)
return [ 'userless' => null, ];
您可以在设备注册时激活无用户登录,也称为一键登录或无键盘登录。在这种情况下,应将其更改为preferred
,因为并非所有设备都支持此功能。
如果此功能被激活(不是null
或discouraged
),则登录验证将是强制性的。
这不会影响登录过程,只会影响认证(注册)。
唯一
return [ 'unique' => false, ];
如果为真,则设备将仅通过设备创建一个凭证。这是通过告诉设备用户已有的凭证ID列表来实现的。如果在设备中已存在至少一个,则后者将返回错误。
密码回退
return [ 'fallback' => true, ];
默认情况下,此包允许在凭证不是WebAuthn JSON有效负载时,使用相同的eloquent-webauthn
驱动程序使用密码登录用户。
禁用回退选项将仅验证WebAuthn凭证。要处理经典用户/密码场景,您可能需要创建一个单独的守卫。
确认超时
return [ 'confirm_timeout' => 10800, ];
当使用确认中间件时,确认将在设定的秒数内被记住。默认为3小时,这对于大多数场景来说已经足够。
证言和元数据语句支持
如果您需要非常高的安全性,您应使用认证和元数据声明。您基本上会要求认证器证明其真实性,并以多种方式对其进行检查。
为此,请查看此文章并根据需要扩展服务容器中的类。
<?php use Webauthn\AttestationStatement\AttestationStatementSupport; use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; $this->app->extend(AttestationStatementSupport::class, function ($manager) { $manager->add(new AndroidSafetyNetAttestationStatementSupport()); });
安全
这是关于此WebAuthn实现的一些详细信息。
- 注册(认证)专属于域名、IP和用户。
- 登录(断言)如果指定,则专属于域名、IP和用户。
- 缓存的挑战在解决后总是会忘记,无论结果如何。
- 缓存的挑战TTL与WebAuthn超时相同(默认60秒)。
- 包含的控制器包括对WebAuthn端点的节流。
- 用户ID(句柄)是随机UUID v4。
- 凭证可以被列入黑名单(启用/禁用)。
如果您发现任何安全问题,请通过电子邮件发送至 darkghosthunter@gmail.com 而不是使用问题跟踪器。
顺便说一句,如果您所在的应用程序位于负载均衡器后面,请记得 正确配置应用程序。
常见问题解答
- 这个功能是否与任何浏览器兼容?
是的。对于旧浏览器,您应该有一个后备检测脚本。这可以通过 包含的JavaScript辅助器 轻松实现。
if (! Larapass.supportsWebAuthn()) { alert('Your device is not secure enough to use this site!'); }
- 这会将用户的指纹、PIN码或图案存储在我的网站上吗?
不。它存储由设备生成的公钥。
- 钓鱼网站能否窃取WebAuthn凭据并在我的网站上使用它们?
不。WebAuthn终结了钓鱼。
- WebAuthn数据能否识别特定设备?
不,除非明确请求并获得同意。
- 我的用户的传统密码安全吗?
是的,只要您像应该那样对它们进行散列,并且您已经保护了您的应用程序密钥。Laravel默认会这样做。您还可以 禁用它们。
- 用户能否注册两个或更多的 设备?
是的。
- 如果凭据被克隆会发生什么?
由于“登录”计数器将大于原始设备报告的计数,用户将无法通过身份验证。要干预此过程,请修改服务容器中的Assertion Validator并添加自己的 CounterChecker
。
$this->app->bind(CounterChecker::class, function () { return new \App\WebAuthn\MyCountChecker; });
在您的计数器检查器中,您可能希望在计数器低于报告的值时抛出异常。
<?php namespace App\WebAuthn; use Webauthn\Counter\CounterChecker; use App\Exceptions\WebAuthn\CredentialCloned; use Webauthn\PublicKeyCredentialSource as Credentials; class MyCountChecker implements CounterChecker { public function check(Credentials $credentials, int $currentCounter) : void { if ($credentials->getCounter() <= $currentCounter) { throw new CredentialCloned($credentials); } } }
- 如果用户丢失了他的设备,他能否注册新设备?
是的,使用这些恢复辅助器。
- 禁用和删除凭据之间的区别是什么?
禁用凭据不会删除它,因此可以在用户恢复它的情况下稍后手动启用。当凭据被删除时,它将永远消失。
- 用户能否从其设备中删除其凭据?
是的。如果这样做,服务器上凭据的其余部分将几乎被孤儿化。您可能希望向用户显示注册凭据列表,以便删除它们。
- 这对密码或双因素认证有多安全?
极其安全,因为它仅在HTTPS(或localhost
)上工作,且没有密码交换,也没有代码在屏幕上可见。
- 我能禁用密码回退吗?我能强制仅使用WebAuthn身份验证吗?
是的。只需确保 使用恢复辅助器 以避免锁定用户。
- 这包含前端JavaScript吗?
是的,但它非常 基本。
- 这会自动在前端编码/解码字符串吗?
是的,包含的 WebAuthn Helper 会为您自动完成。
- 这包括凭据恢复路由吗?
- 我能否通过PC桌面/笔记本电脑/终端将我的智能手机用作认证器?
这取决于操作系统和硬件。一些将需要先将设备配对到“帐户”。其他一些将仅与USB密钥一起工作。这取决于硬件和软件供应商。
- 为什么我的设备没有显示Windows Hello/TouchId/FaceId/指纹认证?
默认情况下,这个WebAuthn实现几乎接受一切。设备、操作系统和Web浏览器的某些组合可能在可用的WebAuthn身份验证方面有所不同。换句话说,这不是我的错。
- 我正在尝试在我的开发服务器上测试这个,但它不起作用
请仅使用 localhost
,或者使用 ngrok(或类似工具)通过 HTTPS 隧道连接您的网站。WebAuthn 仅在 localhost
或仅通过 HTTPS
下工作。
许可
本软件遵循 MIT 许可协议。有关更多信息,请参阅 许可文件。
Laravel 是 Taylor Otwell 的商标。版权所有 © 2011-2020 Laravel LLC。