webauthn登录逻辑调整

This commit is contained in:
Jerry Yan 2022-08-11 10:43:33 +08:00
parent cc57e4bb18
commit 6632d8cea1
Signed by: q792602257
GPG Key ID: D070F653AF6C0004
6 changed files with 64 additions and 45 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\User;
use App\WebAuthn\Repository\PublicKeyCredentialSourceRepositoryImpl; use App\WebAuthn\Repository\PublicKeyCredentialSourceRepositoryImpl;
use App\WebAuthn\WebAuthnService; use App\WebAuthn\WebAuthnService;
use Cose\Algorithm\Manager; use Cose\Algorithm\Manager;
@ -32,11 +33,6 @@ use Webauthn\TokenBinding\IgnoreTokenBindingHandler;
class UserWebAuthnController extends BaseController class UserWebAuthnController extends BaseController
{ {
public function webauthn_login(Request $request)
{
return view("user.webauthn.login");
}
public function register_options(Request $request): PublicKeyCredentialCreationOptions public function register_options(Request $request): PublicKeyCredentialCreationOptions
{ {
$userEntity = new PublicKeyCredentialUserEntity( $userEntity = new PublicKeyCredentialUserEntity(
@ -49,17 +45,44 @@ class UserWebAuthnController extends BaseController
return WebAuthnService::createRequestOptions($userEntity, $challenge); return WebAuthnService::createRequestOptions($userEntity, $challenge);
} }
public function login_options(Request $request): PublicKeyCredentialRequestOptions public function login_options(Request $request)
{ {
$challenge = random_bytes(32); $challenge = random_bytes(32);
$request->session()->put("webauthn_login_challenge", $challenge); $request->session()->put("webauthn_login_challenge", $challenge);
$username = $request->post("username", "");
if ($username) {
$query = User::query();
if (str_contains($username, "@")) {
$query->where("email", "=", $username);
} else {
$query->where("name", "=", $username);
}
$user = $query->first();
if ($user) {
$userHandle = (string) $user->id;
} else {
return new Response([
"success" => false,
"code" => 401,
"message" => "无此用户"
], 401);
}
} else {
$userHandle = "0";
}
$request->session()->put("webauthn_login_user", $userHandle);
$publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions( $publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
$challenge $challenge
); );
$publicKeyCredentialRequestOptions->setUserVerification( $publicKeyCredentialRequestOptions->setUserVerification(
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
); );
$publicKeyCredentialRequestOptions->allowCredentials([]); $publicKeyCredentialSources = WebAuthnService::getPublicKeyCredentialSourceRepository()->findAllForUserEntity(
new PublicKeyCredentialUserEntity("", $userHandle, "")
);
array_map(function ($source) use ($publicKeyCredentialRequestOptions) {
$publicKeyCredentialRequestOptions->allowCredential($source->getPublicKeyCredentialDescriptor());
} ,$publicKeyCredentialSources);
return $publicKeyCredentialRequestOptions; return $publicKeyCredentialRequestOptions;
} }
@ -120,7 +143,24 @@ class UserWebAuthnController extends BaseController
$publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions( $publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
$request->session()->remove("webauthn_login_challenge") $request->session()->remove("webauthn_login_challenge")
); );
$publicKeyCredentialSources = WebAuthnService::getPublicKeyCredentialSourceRepository()->findAllForUserEntity(
new PublicKeyCredentialUserEntity("", "0", "")
);
$publicKeyCredential = WebAuthnService::getPublicKeyCredentialLoader()->loadArray($request->json()->all()); $publicKeyCredential = WebAuthnService::getPublicKeyCredentialLoader()->loadArray($request->json()->all());
$userHandle = null;
foreach ($publicKeyCredentialSources as $source) {
if ($source->getPublicKeyCredentialId() === $publicKeyCredential->getRawId()) {
$userHandle = $source->getUserHandle();
break;
}
}
if ($userHandle === null) {
return new Response([
"success" => false,
"code" => 401,
"message" => "无此密钥"
], 401);
}
$authenticatorAssertionResponse = $publicKeyCredential->getResponse(); $authenticatorAssertionResponse = $publicKeyCredential->getResponse();
if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) { if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) {
//e.g. process here with a redirection to the public key login/MFA page. //e.g. process here with a redirection to the public key login/MFA page.
@ -136,7 +176,7 @@ class UserWebAuthnController extends BaseController
$authenticatorAssertionResponse, $authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions, $publicKeyCredentialRequestOptions,
ServerRequest::fromGlobals(), ServerRequest::fromGlobals(),
$authenticatorAssertionResponse->getUserHandle(), $userHandle,
["localhost"] ["localhost"]
); );
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -3,6 +3,7 @@
namespace App\WebAuthn\Repository; namespace App\WebAuthn\Repository;
use App\Models\WebauthnCredential; use App\Models\WebauthnCredential;
use Illuminate\Database\Eloquent\Builder;
use Webauthn\PublicKeyCredentialSource; use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialSourceRepository; use Webauthn\PublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialUserEntity; use Webauthn\PublicKeyCredentialUserEntity;
@ -52,18 +53,18 @@ class PublicKeyCredentialSourceRepositoryImpl implements PublicKeyCredentialSour
/** /**
* @var WebauthnCredential * @var WebauthnCredential
*/ */
return WebauthnCredential::query()->where(function ($query) use ($publicKeyCredentialId) { return WebauthnCredential::query()->where(function (Builder $query) use ($publicKeyCredentialId) {
$query->where("credential_id", "=", base64_encode($publicKeyCredentialId)); $query->where("credential_id", "=", base64_encode($publicKeyCredentialId));
})->first(); })->first();
} }
private function findAllModelByTypeFree(): array private function findAllModelByTypeFree(): array
{ {
return WebauthnCredential::query()->where("type_free", "=", "1")->get()->toArray(); return WebauthnCredential::query()->where("type_free", "=", "1")->get()->all();
} }
private function findAllModelByUserId(string $userId): array private function findAllModelByUserId(string $userId): array
{ {
return WebauthnCredential::query()->where("user_id", "=", $userId)->get()->toArray(); return WebauthnCredential::query()->where("user_id", "=", $userId)->get()->all();
} }
} }

View File

@ -13,13 +13,16 @@ const webauthn_login = useLogin({
window.webauthn_register = webauthn_register; window.webauthn_register = webauthn_register;
window.webauthn_login = webauthn_login; window.webauthn_login = webauthn_login;
(function() { (function() {
const loginForm = window.document.getElementById("webauthn_login_form"); const button = window.document.getElementById("do_webauthn_login");
console.log(loginForm) if (button) {
if (loginForm) { button.addEventListener("click", function (e) {
loginForm.addEventListener("submit", function (e) { const loginForm = window.document.getElementById("webauthn_login_form");
if (!loginForm) return;
e.preventDefault(); e.preventDefault();
const formData = new FormData(loginForm) const formData = new FormData(loginForm)
webauthn_login(formData) webauthn_login({
username: formData.get("username")
})
.then(() => { .then(() => {
// 成功登录 // 成功登录
window.location.href = "/" window.location.href = "/"

View File

@ -4,10 +4,11 @@
<title>登录</title> <title>登录</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="{{ mix('/css/app.css') }}" rel="stylesheet"/> <link href="{{ mix('/css/app.css') }}" rel="stylesheet"/>
<script src="{{ mix('/js/manifest.js') }}" rel="script"></script>
</head> </head>
<body> <body>
@include("common.header") @include("common.header")
<form class="w-full lg:w-1/2 border-2 mx-auto my-2" action="" method="post" enctype="multipart/form-data"> <form class="w-full lg:w-1/2 border-2 mx-auto my-2" id="webauthn_login_form" action="" method="post" enctype="multipart/form-data">
<div class="block text-lg font-bold ml-4 mt-2">登录</div> <div class="block text-lg font-bold ml-4 mt-2">登录</div>
@csrf @csrf
<label class="block my-2"> <label class="block my-2">
@ -25,11 +26,12 @@
@include("common.form_error") @include("common.form_error")
<div class="block my-2 text-center"> <div class="block my-2 text-center">
<input class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white" type="submit" value="登录"> <input class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white" type="submit" value="登录">
<a class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white" href="{{ url(route("login.webauthn")) }}"> <a class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white" id="do_webauthn_login" href="javascript: void 0">
免输入登录<sup class="text-xs bg-yellow-600 text-white">Alpha</sup> 免输入登录<sup class="text-xs bg-yellow-600 text-white">Alpha</sup>
</a> </a>
</div> </div>
</form> </form>
@include("common.footer") @include("common.footer")
</body> </body>
<script src="{{ mix('/js/webauthn.js') }}" rel="script"></script>
</html> </html>

View File

@ -1,26 +0,0 @@
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>WebAuthn登录</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="{{ mix('/css/app.css') }}" rel="stylesheet"/>
<script src="{{ mix('/js/manifest.js') }}" rel="script"></script>
</head>
<body>
@include("common.header")
<form class="w-full lg:w-1/2 border-2 mx-auto my-2" id="webauthn_login_form" action="javascript:void 0" method="post" enctype="multipart/form-data">
<div class="block text-lg font-bold ml-4 mt-2">登录</div>
@csrf
<label class="block my-2">
用户名
<input class="form-input border-0 border-b-2 w-full" type="text" name="username" value="{{ old("username") }}" placeholder="用户名或邮箱">
</label>
@include("common.form_error")
<div class="block my-2 text-center">
<input class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white" type="submit" value="免输入登录">
</div>
</form>
@include("common.footer")
</body>
<script src="{{ mix('/js/webauthn.js') }}" rel="script"></script>
</html>

View File

@ -26,7 +26,6 @@ Route::post('/upload', ["\\App\\Http\\Controllers\\FileController","upload"]);
// 用户部分 // 用户部分
Route::get('/login', ["\\App\\Http\\Controllers\\UserController", "login_page"])->name("login"); Route::get('/login', ["\\App\\Http\\Controllers\\UserController", "login_page"])->name("login");
Route::post('/login', ["\\App\\Http\\Controllers\\UserController", "authenticate"])->name("login.submit"); Route::post('/login', ["\\App\\Http\\Controllers\\UserController", "authenticate"])->name("login.submit");
Route::get('/login/webauthn/', ["\\App\\Http\\Controllers\\UserWebAuthnController", "webauthn_login"])->name("login.webauthn");
Route::post("/login/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_options"])->name("login.webauthn.options"); Route::post("/login/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_options"])->name("login.webauthn.options");
Route::post("/login/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_validate"])->name("login.webauthn.submit"); Route::post("/login/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_validate"])->name("login.webauthn.submit");
Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register"); Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register");