Day 4:登录与 Token——桌面端怎么存密钥

Day 4 讲 AnchorChat 登录:OAuth 换 Token,electron-store 经 IPC 持久化,主进程 axios 自动带 Bearer。


开场:空壳能跑了,但谁都不能进

Day 2 弹窗,Day 3 穿上标题栏和托盘------老王双击图标,看见的还是 登录页。他说:「像样了,但我进不去啊。」

Day 4 解决 AnchorChat 自己的账号登录:用户名密码换 Token,Token 存哪儿、主进程翻译 API 怎么带鉴权、退出怎么清干净。

登录 是什么 存哪儿
AnchorChat 账号 我们后端 OAuth,管翻译配额、工单、聊天记录上报 electron-store
IM 平台账号 Telegram / WhatsApp Web 里扫码或 Cookie webview partition

两套登录态 别混。客服登 AnchorChat 成功,不代表 Telegram 已在线------那是两个房间两把钥匙。

登录页:表单 → 加密 → OAuth

路由默认 / 重定向 /loginLogin.vue 里就是账号密码 + 登录按钮,逻辑浓缩成:

typescript 复制代码
function oauthTokenFuc() {
  loginRef.value?.validate((valid) => {
    if (!valid) return
    loading.value = true
    const params = {
      username: loginForm.username,
      password: encryptData(loginForm.password), // 传输前加密
      tenantId: '000000',
      grantType: 'password',
      version: version.value,
    }
    userStore.oauthToken(params).then(() => {
      router.replace('/main')
    }).finally(() => {
      loading.value = false
    })
  })
}

接口走 OAuth2 密码模式(示例路径 /blade-auth/oauth/v2/token),真实域名在 .envVITE_BASE_URL

Pinia userStore 收到 access_token 后做三件事:

typescript 复制代码
window.electronStore.setItem('userInfo', JSON.stringify(res))
setToken(res.access_token)
if (res.refresh_token) setRefreshToken(res.refresh_token)

然后 router.replace('/main')------三栏主界面我们后面边做边讲。

密钥存哪儿:electron-store + IPC

浏览器里常用 localStorage,Electron 桌面端我们更倾向 electron-store------数据落在用户目录 JSON 文件里,主进程和渲染进程都能读。

渲染进程 不能直接 require('electron-store')(安全模型不允许)。preload 暴露 electronStore

typescript 复制代码
contextBridge.exposeInMainWorld('electronStore', {
  setItem: (key, value) => {
    store[key] = value
    ipcRenderer.send('setStore', key, value)
  },
  getItem: (key) => {
    let value = store[key]
    if (!value) {
      value = ipcRenderer.sendSync('getStore', key)
      store[key] = value
    }
    return value
  },
  removeItem: (key) => {
    delete store[key]
    ipcRenderer.send('deleteStore', key)
  },
})

主进程启动时注册 IPC,并持有同一个 Store 实例:

typescript 复制代码
import Store from 'electron-store'
export const store = new Store()

export function elestore() {
  ipcMain.on('setStore', (_event, key, value) => {
    store.set(key, value)
  })
  ipcMain.on('getStore', (event, key) => {
    event.returnValue = store.get(key)
  })
  ipcMain.on('deleteStore', (_event, key) => {
    store.delete(key)
  })
}

app.whenReady() 里调用 elestore()------和 Day 3 的 registerWindowControls 一样,属于主进程「基础设施注册」。

渲染层 src/utils/auth.ts 封装 Token 读写,键名例如 vue_tokenvue_refresh_token

主进程也要调 API:auth-ele + axios 拦截器

翻译、聊天记录上报跑在 主进程 (Node axios),不能指望渲染进程把 Token 每次 invoke 传过来------直接从 store 读:

typescript 复制代码
import { store } from './elestore'

export function getToken() {
  return store.get('vue_token')
}

export function setToken(token: string) {
  return store.set('vue_token', token)
}

electron/utils/request.ts 请求拦截器自动带头:

typescript 复制代码
if (getToken() && !isToken) {
  config.headers['Blade-Auth'] = 'Bearer ' + getToken()
  const refreshToken = getRefreshToken()
  if (refreshToken) {
    config.headers['X-Refresh-Token'] = refreshToken
  }
}

响应里若后端下发新 Token(响应头 x-new-access-token),拦截器会 写回 store ------桌面端可以长时间开着,不用每天重新登录。细节实现各团队不同,AnchorChat 的思路是:一处存储,渲染 + 主进程共用

退出登录:别留 ghost Token

标题栏退出时,除了调后端 logout,还要清本地:

  • electronStore 里的 userInfovue_token
  • Pinia 内存态
  • 必要时清 IM 相关缓存

否则下次打开以为已登录,主进程 axios 却带着过期 Bearer,接口全 401------调试能调到你怀疑人生。

踩坑与思考

  • 渲染进程 setToken 了,主进程读不到 :检查 setStore IPC 是否注册、elestore() 是否在 whenReady 里调用
  • getStore 用 sendSync :首次读盘会阻塞渲染进程一小下,键别存巨型 JSON;userInfo 够用就别把整个账号列表塞进去
  • 密码明文进 store :我们只存 Token 和加密后的「记住账号」可选字段,不要把明文 password 写进 electron-store
  • OAuth Client Secret 别写进 preload:Basic 认证头放主进程或构建时 env
  • 和 IM Cookie 混淆 :webview 里的三方 IM登录,Cookie 在 partition 里,不会 自动进 vue_token

明日预告

Day 5 终于进 三栏主界面Main.vue + El-Splitter,左账号、中聊天、右工具------登录进来不再只看登录页,而是「工作台」骨架。webview 还要再等 Day 6,别急。


你们桌面端 Token 放 electron-store 还是 keytar/系统钥匙串?欢迎评论区交流

相关推荐
溯朢1 小时前
TokUI 流式渲染的 SSE 全链路拆解
前端
京东云开发者1 小时前
京东 Oxygen xLLM 大模型推理引擎正式捐赠开放原子开源基金会,共建国产 AI Infra 生态
前端
Csvn1 小时前
LLM 一把梭:从 Swagger 文档到类型安全 API 请求,再也不手写接口
前端
DGT1 小时前
深入理解 JavaScript 闭包
前端
星栈1 小时前
Dioxus 表单处理:从输入、校验到文件上传,一条链路讲透
前端·rust·前端框架
用户41659673693551 小时前
WebView 请求异常排查操作手册
android·前端
weedsfly1 小时前
JavaScript 事件流:彻底搞懂捕获、冒泡与事件委托
前端·javascript·react.js
RainmeoX1 小时前
【实战】用纯前端打造绝区零风格 AI 角色助手 WebUI 并联调 vLLM
前端