拥抱无密码时代:Golang+Vue中使用Passkey构建安全登录系统
引言
随着网络安全意识的提升和用户对便捷性的追求,传统的密码认证方式正逐渐显现出其局限性。Apple在2022年WWDC上推出的Passkey技术,为这一挑战提供了革命性的解决方案。Passkey作为一种基于WebAuthn标准的强身份验证方法,利用公钥加密确保了极致的安全性,同时简化了用户的登录体验。7月13日 作为全球最大同性交友社区的github开始公测Passkey,标志着其进入无密码身份验证阶段。我们也紧跟步伐,本文将结合Golang与Vue实现一个支持Passkey的登录系统。
为什么选择Passkey?
安全性:Passkey使用高级加密算法来保护用户的密码。
易用性:用户界面简洁,易于使用。
跨平台:支持多种操作系统和设备。
技术概览
Passkey:一种无密码认证方式,通过用户的生物识别或设备特定认证(如Touch ID/Face ID),生成一对公私钥,公钥存储于服务器,私钥则安全地保存在用户设备上,实现了零知识证明的安全登录。
Golang
Vue.js
后端实现(Golang)
使用go-webauthn/webauthn这个包来实现WebAuthn功能,我们将创建一个基本的注册与验证示例。首先,确保安装了这个包:
go get github.com/go-webauthn/webauthn
接下来是go-webauthn/webauthn包的简化示例代码,涵盖用户注册新设备和通过已注册设备验证的过程:
package main
import (
"context"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/duo-labs/go-webauthn/webauthn"
"github.com/duo-labs/webauthn/protocol"
)
var web *webauthn.WebAuthn
var credentials map[string]*webauthn.Credential // 假设这是一个全局存储,实际应用中应替换为数据库
func init() {
// 初始化WebAuthn配置
rp := protocol.RelyingParty{
Name: "My App",
ID: "localhost",
}
config := &webauthn.Config{
RPID: rp.ID,
RPOrigin: "http://localhost:8080",
EffectiveDomain: "localhost",
AttestationType: protocol.PreferNoAttestation,
SupportedExtensions: []protocol.ExtensionID{protocol.HMACSecretExtensionID},
}
web = webauthn.New(config)
}
func main() {
gin.SetMode(gin.ReleaseMode)
router := gin.Default()
router.POST("/webauthn/register/begin", beginRegistration)
router.POST("/webauthn/register/finish", finishRegistration)
router.POST("/webauthn/authenticate/begin", beginAuthentication)
router.POST("/webauthn/authenticate/finish", finishAuthentication)
if err := router.Run(":8080"); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}
func beginRegistration(c *gin.Context) {
user := webauthn.User{
ID: []byte("user_unique_id"), // 应该是唯一的用户标识符
Name: "John Doe",
DisplayName: "John",
}
regOptions, sessionData, err := web.BeginRegistration(context.Background(), user)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 应保存sessionData用于后续步骤
_ = sessionData // 实际应用中需要存储sessionData
c.JSON(http.StatusOK, gin.H{
"registrationOptions": regOptions,
})
}
func finishRegistration(c *gin.Context) {
var regReq protocol.ParsedRegistrationData
err := json.NewDecoder(c.Request.Body).Decode(®Req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid registration data"})
return
}
// 这里假设sessionData已经从之前步骤获取
sessionData := []byte{} // 应从数据库或会话中获取
cred, err := web.FinishRegistration(context.Background(), sessionData, regReq)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 实际应用中,需要将cred.ID和cred.PublicKey等存入数据库
credentials[cred.ID.String()] = cred
c.JSON(http.StatusOK, gin.H{"message": "Registration successful"})
}
func beginAuthentication(c *gin.Context) {
// 从请求中获取用户ID或用户名
username := c.PostForm("username")
// 从数据库中查找对应的credentials
cred, ok := credentials[username]
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"})
return
}
authOptions, sessionData, err := web.BeginAuthentication(context.Background(), *cred)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 应保存sessionData用于后续步骤
_ = sessionData // 实际应用中需要存储sessionData
c.JSON(http.StatusOK, gin.H{
"authenticationOptions": authOptions,
})
}
func finishAuthentication(c *gin.Context) {
var authReq protocol.ParsedAssertionData
err := json.NewDecoder(c.Request.Body).Decode(&authReq)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid authentication data"})
return
}
// 这里假设sessionData已经从之前步骤获取
sessionData := []byte{} // 应从数据库或会话中获取
_, err = web.FinishAuthentication(context.Background(), sessionData, authReq)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication failed"})
return
}
// 验证成功
c.JSON(http.StatusOK, gin.H{"message": "Authentication successful"})
}
上述代码使用到了go-webauthn/webauthn这个包,仅为简化示例,实际部署时需考虑安全性、错误处理、会话管理及数据库集成等。
使用HTTPS是WebAuthn要求的安全实践,需要在https下才能使用。
前端Vue实现
首先,确保安装了Vue CLI和创建了一个新的Vue项目。然后,安装@github/webauthn-json库来处理WebAuthn的客户端逻辑:
npm install @github/webauthn-json
简易代码示例:
<template>
<div id="app">
<h1>Passkey 示例</h1>
<button @click="startRegistration" v-if="!isRegistering && !isAuthenticated">注册 Passkey</button>
<button @click="startAuthentication" v-if="!isRegistering && !isAuthenticated" :disabled="!hasPasskeyRegistered">使用 Passkey 登录</button>
<div v-if="isRegistering">
<p>注册过程中...</p>
<button @click="cancelRegistration">取消</button>
</div>
<div v-if="isAuthenticated">
<p>登录成功!</p>
<button @click="logout">登出</button>
</div>
</div>
</template>
<script>
import WebAuthn from '@github/webauthn-json';
export default {
name: 'App',
data() {
return {
isRegistering: false,
isAuthenticated: false,
hasPasskeyRegistered: false, // 假设此状态由后端告知
};
},
methods: {
async startRegistration() {
this.isRegistering = true;
try {
const response = await fetch('/api/register/begin', { method: 'POST' });
if (!response.ok) throw new Error('Failed to start registration');
const options = await response.json();
const credential = await WebAuthn.create(options);
const registerResponse = await fetch('/api/register/finish', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credential),
});
if (!registerResponse.ok) throw new Error('Failed to finish registration');
console.log('Passkey registered successfully');
this.hasPasskeyRegistered = true;
this.isRegistering = false;
} catch (error) {
console.error('Registration error:', error);
this.isRegistering = false;
}
},
async startAuthentication() {
try {
const response = await fetch('/api/authenticate/begin', { method: 'POST' });
if (!response.ok) throw new Error('Failed to start authentication');
const options = await response.json();
const assertion = await WebAuthn.get(options);
const authResponse = await fetch('/api/authenticate/finish', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(assertion),
});
if (!authResponse.ok) throw new Error('Failed to finish authentication');
console.log('Passkey authenticated successfully');
this.isAuthenticated = true;
} catch (error) {
console.error('Authentication error:', error);
}
},
cancelRegistration() {
this.isRegistering = false;
},
logout() {
this.isAuthenticated = false;
},
},
};
</script>
结语
Passkey提供了一个简单而安全的密码管理解决方案。通过Go语言的后端和Vue.js的前端,我们能够快速构建一个高效且用户友好的应用。
尽管示例为简化版,但希望它能作为一把钥匙,开启您对Passkey技术深入研究与实践的大门。在未来的开发旅程中,不断探索、优化,将这项前沿技术融入到更多的应用场景中,共同推动互联网安全领域的进步,为用户创造更加安全、便捷的数字生活体验。