IKE(Internet Key Exchange)并不是一个能直接在浏览器里跑完的协议。它需要构造 UDP 报文,在 500 和 4500 端口上完成 DH 交换、身份认证和 SA 参数协商。浏览器对原始 UDP 套接字的限制,决定了 Vue 3 工程里不可能用纯前端代码实现真正的 IKE 客户端。更务实的做法是:后端通过 strongSwan、Libreswan 或自研服务承载 IKE 协商,Vue 3 负责把配置、状态和日志组织成可操作的管理界面。这个过程中,如果只是简单调用接口,很快会遇到状态不同步、配置散落、错误提示不一致等问题,因此需要一层工程化抽象。

一、为什么需要独立的 IKE 抽象层
IKE 协商参数比普通 HTTP 接口复杂得多。以 IKEv2 为例,一次完整的协商需要确认加密算法、伪随机函数、完整性算法、DH 组、认证方式、SA 生命周期、DPD 超时和 NAT-T 行为。这些参数在不同实现里的命名并不一致,strongSwan 使用 ike=aes256-sha256-modp2048 这样的字符串,而 Libreswan 的配置文件又采用 ikev2=insist 加 esp 行描述。若把这些差异直接写进 Vue 组件,一个字段改名就会引发多处修改。
工程化的第一步是建立独立于组件的数据模型。下面这个类型定义把协商提案和会话状态拆开,前端不关心后端是 strongSwan 还是 Libreswan,只依赖稳定的 TypeScript 接口。
export interface IkeProposal {
encryption: 'aes256' | 'aes128' | 'chacha20poly1305'
integrity: 'sha256' | 'sha384' | 'sha512'
dhGroup: 14 | 15 | 16 | 19 | 20
}
export interface IkeSessionState {
phase1: 'idle' | 'connecting' | 'established' | 'failed'
phase2: 'idle' | 'ready' | 'expired'
remotePeer: string
localEndpoint: string
lastError?: string
}
类型定义的价值不在代码量,而在于约束。后端返回的加密算法可以先用映射函数转成前端枚举,组件再根据枚举渲染下拉框。这样即使后端升级了算法集合,也只需要调整映射层,不会把兼容性问题扩散到各个页面。
二、用组合式 API 封装协商状态
IKE 协商不是毫秒级完成的请求。从发送 IKE_SA_INIT 到完成 IKE_AUTH,中间可能经历网络丢包、重传、证书校验和用户认证,耗时从几百毫秒到几十秒不等。因此前端不能用一次 fetch 就假设结果返回。更适合的模式是轮询加推送:后端暴露状态查询接口,前端定时拉取,同时通过 WebSocket 或 SSE 接收关键事件。
下面用组合式 API 封装轮询逻辑。这个 composable 接收对端地址,返回响应式状态,并在组件卸载时自动清理定时器,避免页面切换后仍然继续请求。
import { ref, onMounted, onUnmounted } from 'vue'
export function useIkeNegotiation(peer: string) {
const state = ref<IkeSessionState>({
phase1: 'idle',
phase2: 'idle',
remotePeer: peer,
localEndpoint: ''
})
let timer: number | undefined
async function pollStatus() {
const response = await fetch(`/api/ike/status?peer=${encodeURIComponent(peer)}`)
const data = await response.json()
state.value = data
}
onMounted(() => {
pollStatus()
timer = window.setInterval(pollStatus, 3000)
})
onUnmounted(() => window.clearInterval(timer))
return { state, pollStatus }
}
单个会话的轮询可以这样处理,但真实项目里通常同时存在多个 VPN 隧道。把所有状态都放在组件内部会很难共享,例如顶部导航需要显示当前在线隧道数,详情页又要展示某个隧道的二阶段 SA 过期时间。此时可以引入 Pinia,把会话列表和更新动作集中管理。组件只调用 store 的更新方法,不直接修改状态对象,这样数据流向清晰,调试时也容易跟踪。
import { defineStore } from 'pinia'
import type { IkeSessionState } from './types'
export const useIkeStore = defineStore('ike', {
state: () => ({
sessions: [] as IkeSessionState[],
activeSessionId: null as string | null
}),
actions: {
updateSession(id: string, patch: Partial<IkeSessionState>) {
const session = this.sessions.find(s => s.id === id)
if (session) Object.assign(session, patch)
}
}
})
这里的 Partial<IkeSessionState> 允许只更新部分字段。后端推送的协商事件往往只包含 phase1 或 phase2 的变化,用部分更新可以避免前端伪造完整对象。
三、Vite 代理与安全边界
开发环境下,Vue 3 工程通常运行在 localhost:5173,而 IKE 管理服务可能监听在 8080 端口。直接跨域调用会触发 CORS 限制,而且把后端地址暴露在前端代码里也不利于部署。Vite 的 server.proxy 可以把 /api/ike 前缀的请求转发到真实服务,前端只写相对路径。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api/ike': {
target: 'http://127.0.0.1:8080',
changeOrigin: true
}
}
}
})
这段配置不会进入生产环境,生产部署通常由 Nginx、Caddy 或 Kubernetes Ingress 完成同样的路径转发。把 /api/ike 作为统一前缀的好处是,前端不需要知道后端服务实际运行在哪台机器,也不需要在代码里写死 IP 和端口。
安全边界需要在这一层同时确定。IKE 的预共享密钥绝对不能放入 Vue 组件或 Pinia store。浏览器端只应保存密钥的名称、指纹或掩码,真实 PSK 由后端在发起协商时从密钥管理系统读取。同理,私钥和证书文件也不应该通过接口返回给前端。证书指纹可以展示给管理员确认,但完整证书内容应限制在后端。
另一个容易忽略的点是输入校验。对端地址、加密算法和 DH 组等参数虽然最终由后端校验,但前端做第一层过滤可以减少无效请求。对端地址至少要验证是否为合法 IPv4/IPv6 或域名,避免把未完成的值发送给后端。这样既能改善用户体验,也能减少管理接口的异常日志。
四、配置表单与参数校验的工程化实践
IKE 配置表单如果只靠 v-model 和若干 if 条件,很快会变得难以维护。IKEv1 需要区分主模式和野蛮模式,IKEv2 的认证方式又包括证书、PSK 和 EAP。不同认证方式下,表单字段差异很大。更工程化的做法是用 schema 描述字段,组件根据 schema 动态渲染,而不是为每种模式复制一份表单。
下面是一个简化的校验函数,它把提案对象中不合法的组合收集成错误数组。功能上虽然简单,但集中了规则,后续增加算法或 DH 组时只需要改这一处。
export function validateProposal(proposal: IkeProposal): string[] {
const errors: string[] = []
if (!['aes256', 'aes128', 'chacha20poly1305'].includes(proposal.encryption)) {
errors.push('不支持的加密算法')
}
if (![14, 15, 16, 19, 20].includes(proposal.dhGroup)) {
errors.push('DH 组不在允许范围')
}
return errors
}
这些校验只解决参数格式问题,真正的协商失败还需要后端返回结构化错误。前端可以把后端错误码映射为可读信息,例如将 NO_PROPOSAL_CHOSEN 转成“两端没有共同的安全提案”,而不是直接把英文日志丢给用户。对于需要深入排查的场景,可以单独提供日志查看页,但默认界面应保持简洁。
工程化 IKE 管理端的最终目标,是让管理员不必理解底层协议也能完成常见操作,同时为熟悉协议的人保留高级选项。抽象层、状态封装、代理配置和校验规则共同构成了这个目标的基础。Vue 3 的响应式能力和组合式 API 适合处理这类异步、多状态、需要复用的场景,但前提是不要把协议细节散落在组件里。