WebAuthn(Web Authentication API)은 공개 키 암호화를 사용하여 안전한 비밀번호 없는 인증을 가능하게 하는 W3C 표준입니다. 사용자는 비밀번호 대신 하드웨어 기반 인증자(지문 판독기, Face ID, YubiKey 등) 또는 플랫폼 인증자(Touch ID와 같은 내장형)를 사용하여 등록하고 인증합니다.
Javascript 클라이언트를 사용하여 등록 및 인증을 처리하는 방법은 아래에서 확인하십시오.
WebAuthn Registration의 작동 방식
사용자가 등록을 시작합니다:
사용자가 사용자 이름을 제공하고 Register를 클릭합니다.
브라우저가 서버에 Options를 요청함:
프런트엔드가 등록 옵션을 얻기 위해 POST 요청을 합니다.
브라우저가 자격 증명을 생성합니다:
SimpleWebAuthnBrowser.startRegistration()를 통한 navigator.credentials.create() API를 사용하여 자격 증명이 생성됩니다.
서버가 등록을 검증함:
브라우저가 자격 증명을 서버로 다시 보냅니다.
서버가 등록을 검증하고 해당 사용자의 공개 키를 저장합니다.
TsgcWSAPIServer_WebAuthn 구성 요소에는 WebAuthn 프로토콜을 테스트하기 위한 html 파일이 있습니다. 이 HTML 파일에는 WebAuthn과 상호 작용하기 위한 최소한의 UI와 JavaScript가 포함되어 있습니다.
sgcWebAuthn.html 둘러보기
구조 개요:
Username Input: 사용자의 식별자를 캡처합니다.
버튼:
Register – WebAuthn 등록을 시작합니다.
Authenticate – 로그인을 시작합니다(유사하게 처리됨).
Debug Console: 실시간 디버그 정보(WebAuthn의 JSON)를 표시합니다.
1. 입력용 HTML UI
<input type="text" id="username" name="username" autocomplete="username webauthn" />
<button id="btnRegBegin"><strong>Register</strong></button>
2. JavaScript: 버튼 클릭 핸들러
document.querySelector('#btnRegBegin').addEventListener('click', async () => {
const username = document.getElementById("username").value;
if (username == "") {
document.getElementById('Error').innerText = 'Please enter a username to register';
return;
}
const resp = await fetch('<#webauthn_registration_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, algorithms: [] })
});
const options = await resp.json();
const attResp = await startRegistration(options); // WebAuthn API
3. 서버 응답(HTML의 가짜 엔드포인트)
fetch('/sgcWebAuthn/Registration/Options', ...)
fetch('/sgcWebAuthn/Registration/Verify', ...)
4. 등록 완료
const verificationResp = await fetch('/webauthn/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(attResp)
});
const verificationJSON = await verificationResp.json();
if (verificationJSON && verificationJSON.verified) {
document.getElementById('Success').innerHTML = `Authenticator registered!`;
}
WebAuthn 인증 작동 방식
사용자가 인증을 시작합니다:
사용자가 사용자 이름을 제공하고 Register를 클릭합니다.
브라우저가 서버에 Options를 요청함:
프론트엔드가 인증 옵션을 얻기 위해 POST 요청을 합니다.
브라우저가 자격 증명을 생성합니다:
SimpleWebAuthnBrowser.startAuthentication()을 통해 navigator.credentials.get() API를 사용합니다.
서버가 인증을 검증함:
브라우저가 자격 증명을 서버로 다시 전송합니다.
서버가 서명된 결과를 검증합니다.
1. HTML UI 설정
<div class="container">
<h1>WebAuthn Authentication Sample</h1>
<section id="userdata">
<label for="username">Username:</label>
<input type="text" id="username" name="username" autocomplete="username webauthn" autofocus />
</section>
<button id="btnAuthBegin"><strong>Authenticate</strong></button>
<p id="Success" class="success"></p>
<p id="Error" class="error"></p>
<details open>
<summary>Console</summary>
<textarea id="Debug"></textarea>
</details>
</div>
2. Authentication Options 가져오기
startAuthentication을 호출하기 전에 사용자 이름을 서버로 보냅니다
const resp = await fetch('<#webauthn_authentication_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: document.getElementById("username").value,
user_verification: 'preferred'
}),
});
서버는 다음을 포함하는 JSON 객체로 응답합니다:
{
"challenge": "base64url-encoded-random-string",
"allowCredentials": [
{
"id": "base64url-credential-id",
"type": "public-key"
}
],
"userVerification": "preferred",
"rpId": "yourdomain.com"
}
이것을 PublicKeyCredentialRequestOptions라고 합니다.
3. Authenticator 응답 수신
asseResp는 다음과 같습니다(단순화됨):
{
"id": "credentialId",
"rawId": "base64url-encoded-id",
"response": {
"authenticatorData": "...",
"clientDataJSON": "...",
"signature": "...",
"userHandle": "..."
},
"type": "public-key",
"clientExtensionResults": {}
}
이 응답은 사용자가 다음을 증명합니다:
4. 서명된 Authentication Response를 서버에 전송
사용자가 인증자와 상호 작용한 후(startAuthentication()을 통해) JavaScript에서 응답 객체를 얻습니다.
const verificationResp = await fetch('/webauthn/authenticate/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(asseResp), // 서명된 응답입니다
});
5. 서버의 응답 받기
서버는 다음과 같은 결과로 응답합니다:
{ "verified": true }
또는 문제가 발생한 경우:
{ "verified": false, "error": "Invalid signature" }
그리고 프런트엔드 코드에서 이를 처리합니다:
const result = await verificationResp.json();
if (result.verified) {
document.getElementById('Success').textContent = 'User authenticated!';
} else {
document.getElementById('Error').textContent = 'Authentication failed!';
}