# IC Coder 认证系统实现文档 ## 概述 本文档详细说明了 IC Coder 插件如何集成 VSCode Authentication API,实现用户登录功能,并在 VSCode 左下角账户区域显示登录状态。 ## 架构设计 ### 核心组件 1. **ICCoderAuthenticationProvider** - 认证提供者 2. **VSCode Authentication API** - VSCode 官方认证接口 3. **本地 HTTP 服务器** - 处理登录回调 4. **ICViewProvider** - 侧边栏视图(根据登录状态显示不同按钮) ### 工作流程 ``` 用户点击登录 ↓ 调用 vscode.authentication.getSession() ↓ ICCoderAuthenticationProvider.createSession() ↓ 启动本地 HTTP 服务器(动态端口) ↓ 打开浏览器访问登录页面 ↓ 用户在网站完成登录 ↓ 网站重定向到 http://localhost:{port}/callback?token=xxx ↓ 本地服务器接收 token ↓ 创建 AuthenticationSession ↓ VSCode 左下角显示账户信息 ``` ## 详细实现 ### 1. Authentication Provider 实现 文件:`src/services/icCoderAuthProvider.ts` #### 1.1 类定义 ```typescript export class ICCoderAuthenticationProvider implements vscode.AuthenticationProvider { private _onDidChangeSessions = new vscode.EventEmitter(); public readonly onDidChangeSessions = this._onDidChangeSessions.event; private _sessions: vscode.AuthenticationSession[] = []; } ``` **关键点:** - 实现 `vscode.AuthenticationProvider` 接口 - 使用 `EventEmitter` 通知会话变化 - 在内存中维护会话列表 #### 1.2 核心方法 ##### getSessions() - 获取会话列表 ```typescript async getSessions(scopes?: readonly string[]): Promise { return this._sessions; } ``` ##### createSession() - 创建会话(登录) ```typescript async createSession(scopes: readonly string[]): Promise { const token = await this.login(); const session: vscode.AuthenticationSession = { id: this.generateSessionId(), accessToken: token, account: { id: "iccoder-user", label: "IC Coder 用户", }, scopes: [...scopes], }; this._sessions.push(session); await this.saveSessions(); this._onDidChangeSessions.fire({ added: [session], removed: [], changed: [], }); return session; } ``` **关键点:** - 调用 `login()` 方法获取 token - 创建 `AuthenticationSession` 对象 - 保存到 `globalState` - 触发 `onDidChangeSessions` 事件通知 VSCode ##### removeSession() - 删除会话(登出) ```typescript async removeSession(sessionId: string): Promise { const sessionIndex = this._sessions.findIndex((s) => s.id === sessionId); if (sessionIndex > -1) { const session = this._sessions[sessionIndex]; this._sessions.splice(sessionIndex, 1); await this.saveSessions(); this._onDidChangeSessions.fire({ added: [], removed: [session], changed: [], }); } } ``` ### 2. 本地 HTTP 服务器实现 #### 2.1 动态端口分配 ```typescript server.listen(0, () => { const address = server.address(); const port = typeof address === "object" && address ? address.port : 3000; resolve({ server, port }); }); ``` **关键点:** - 使用端口 `0` 让系统自动分配可用端口 - 避免端口冲突问题 - 支持多个用户同时使用 #### 2.2 回调处理 ```typescript if (url.pathname === "/callback") { const token = url.searchParams.get("token"); if (token) { // 返回成功页面 res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(this.getSuccessPage(iconBase64)); // 关闭服务器 server.close(); // 返回 token if ((server as any)._loginResolve) { (server as any)._loginResolve(token); } } } ``` ### 3. package.json 配置 #### 3.1 注册 Authentication Provider ```json { "contributes": { "authentication": [ { "id": "iccoder", "label": "IC Coder" } ] } } ``` **关键点:** - `id` 必须与代码中使用的 ID 一致 - `label` 会显示在 VSCode 账户菜单中 #### 3.2 注册命令 ```json { "contributes": { "commands": [ { "command": "ic-coder.login", "title": "IC Coder: 登录账户", "category": "IC Coder" }, { "command": "ic-coder.logout", "title": "IC Coder: 退出登录", "category": "IC Coder" } ] } } ``` ### 4. extension.ts 注册 #### 4.1 注册 Authentication Provider ```typescript export function activate(context: vscode.ExtensionContext) { // 注册 Authentication Provider const authProvider = new ICCoderAuthenticationProvider(context); context.subscriptions.push( vscode.authentication.registerAuthenticationProvider( "iccoder", "IC Coder", authProvider ) ); } ``` #### 4.2 登录命令 ```typescript const loginCommand = vscode.commands.registerCommand( "ic-coder.login", async () => { try { await vscode.authentication.getSession("iccoder", [], { createIfNone: true, }); } catch (error) { vscode.window.showErrorMessage(`登录失败: ${error}`); } } ); ``` **关键点:** - `createIfNone: true` 会在没有会话时自动调用 `createSession()` - VSCode 会自动处理 UI 交互 #### 4.3 登出命令 ```typescript const logoutCommand = vscode.commands.registerCommand( "ic-coder.logout", async () => { try { const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false, }); if (session) { await vscode.authentication.getSession("iccoder", [], { clearSessionPreference: true, forceNewSession: true, }); vscode.window.showInformationMessage("已退出登录"); } } catch (error) { vscode.window.showInformationMessage("当前未登录"); } } ); ``` ### 5. ICViewProvider 集成 #### 5.1 检查登录状态 ```typescript private async checkLoginStatus(): Promise { try { const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false }); return !!session; } catch (error) { return false; } } ``` #### 5.2 根据登录状态显示不同按钮 ```typescript resolveWebviewView(webviewView: vscode.WebviewView) { this.checkLoginStatus().then((isLoggedIn) => { webviewView.webview.html = this.getWebviewContent( webviewView.webview, isLoggedIn ); }); } ``` ```typescript ${isLoggedIn ? '' : '' } ``` ### 6. 网站前端配置 #### 6.1 检测插件登录请求 ```javascript // 在登录页面检测 redirect_uri 参数 const urlParams = new URLSearchParams(window.location.search); const redirectUri = urlParams.get("redirect_uri"); if (redirectUri) { // 保存回调地址 localStorage.setItem("plugin_redirect_uri", redirectUri); } ``` #### 6.2 登录成功后重定向 ```javascript // 用户登录成功,拿到 token const token = response.data.token; // 检查是否需要重定向回插件 const redirectUri = localStorage.getItem("plugin_redirect_uri"); if (redirectUri) { // 重定向回插件,带上 token window.location.href = `${redirectUri}?token=${token}`; localStorage.removeItem("plugin_redirect_uri"); } else { // 正常登录流程 router.push("/dashboard"); } ``` ## 关键技术点 ### 1. 动态端口分配 **问题:** 固定端口可能被占用,导致登录失败 **解决方案:** 使用端口 `0` 让系统自动分配可用端口 ```typescript server.listen(0, () => { const address = server.address(); const port = typeof address === "object" && address ? address.port : 3000; }); ``` ### 2. Promise 异步等待 **问题:** 需要等待浏览器登录完成后才能继续 **解决方案:** 使用 Promise 包装回调逻辑 ```typescript return new Promise((resolve, reject) => { (server as any)._loginResolve = resolve; (server as any)._loginReject = reject; }); ``` ### 3. 会话持久化 **问题:** 重启 VSCode 后需要重新登录 **解决方案:** 使用 `globalState` 保存会话 ```typescript await this.context.globalState.update("icCoderSessions", this._sessions); ``` ### 4. 事件通知机制 **问题:** VSCode 需要知道会话状态变化 **解决方案:** 使用 `EventEmitter` 触发事件 ```typescript this._onDidChangeSessions.fire({ added: [session], removed: [], changed: [], }); ``` ## 用户体验 ### 登录流程 1. 用户点击侧边栏"登录账户"按钮 2. 浏览器自动打开登录页面 3. 用户在网站完成登录 4. 浏览器自动跳转到成功页面 5. VSCode 左下角显示"IC Coder 用户" 6. 侧边栏按钮变为"开始创作" ### 登出流程 1. 点击 VSCode 左下角账户图标 2. 选择"IC Coder"账户 3. 点击"退出"按钮 4. 或使用命令 `IC Coder: 退出登录` ## 常见问题 ### Q1: 为什么不直接使用 globalState 存储 token? **A:** 使用 VSCode Authentication API 的优势: - ✅ 统一的用户体验(左下角账户区域) - ✅ VSCode 自动管理会话生命周期 - ✅ 支持多账户切换 - ✅ 更好的安全性(VSCode 负责加密存储) ### Q2: 如何处理 token 过期? **A:** 可以在 API 请求失败时: 1. 检测 401 错误 2. 调用 `removeSession()` 清除过期会话 3. 提示用户重新登录 ### Q3: 如何支持多个账户? **A:** 修改 `account` 对象: ```typescript account: { id: userInfo.id, label: userInfo.username, } ``` ### Q4: 登录页面如何获取用户信息? **A:** 可以在登录成功后,通过 API 获取用户信息: ```typescript const userInfo = await fetch("https://api.iccoder.com/user/info", { headers: { Authorization: `Bearer ${token}` }, }); const session: vscode.AuthenticationSession = { account: { id: userInfo.id, label: userInfo.username, }, // ... }; ``` ## 安全考虑 ### 1. Token 存储 - ✅ 使用 VSCode `globalState` 加密存储 - ✅ 不在代码中硬编码敏感信息 - ✅ Token 仅在内存和加密存储中传递 ### 2. 本地服务器 - ✅ 仅监听 `localhost`,不暴露到外网 - ✅ 使用动态端口,避免固定端口被劫持 - ✅ 接收到 token 后立即关闭服务器 - ✅ 设置 5 分钟超时,防止服务器长期运行 ### 3. HTTPS 考虑 **当前实现:** 使用 HTTP 本地回调 **生产环境建议:** - 网站使用 HTTPS - 本地回调使用 HTTP(localhost 不受浏览器限制) - 或使用 `vscode://` 协议(需要网站支持) ## 测试指南 ### 1. 本地测试 ```bash # 启动调试模式 按 F5 # 测试登录 1. 打开侧边栏 2. 点击"登录账户" 3. 在浏览器完成登录 4. 检查左下角是否显示账户 # 测试登出 1. 点击左下角账户 2. 选择"IC Coder" 3. 点击"退出" ``` ### 2. 调试技巧 ```typescript // 在 ICCoderAuthenticationProvider 中添加日志 console.log("🔐 创建会话:", session); console.log("🔑 Token:", token); // 在 ICViewProvider 中添加日志 console.log("🔍 登录状态:", isLoggedIn); ``` ### 3. 常见错误排查 | 错误 | 原因 | 解决方案 | | ------------------------------- | --------------- | ---------------------------------- | | `getSessions is not a function` | VSCode 版本过低 | 升级到 1.63.0+ | | 端口被占用 | 固定端口冲突 | 使用动态端口(已实现) | | 登录后未显示账户 | 未触发事件 | 检查 `_onDidChangeSessions.fire()` | | 重启后需要重新登录 | 未保存会话 | 检查 `saveSessions()` 调用 | ## 文件结构 ``` ic-coder/ ├── src/ │ ├── services/ │ │ └── icCoderAuthProvider.ts # Authentication Provider 实现 │ ├── views/ │ │ └── ICViewProvider.ts # 侧边栏视图(集成登录状态) │ └── extension.ts # 注册 Provider 和命令 ├── package.json # 配置 authentication 和 commands └── docs/ └── authentication-implementation.md # 本文档 ``` ## 参考资料 - [VSCode Authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication) - [Authentication Provider Sample](https://github.com/microsoft/vscode-extension-samples/tree/main/authentication-sample) - [VSCode Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines) ## 总结 本实现通过以下步骤完成了 VSCode Authentication API 的集成: 1. ✅ 创建 `ICCoderAuthenticationProvider` 类实现认证逻辑 2. ✅ 在 `package.json` 中注册 authentication provider 3. ✅ 在 `extension.ts` 中注册 provider 和命令 4. ✅ 实现本地 HTTP 服务器处理登录回调 5. ✅ 使用动态端口避免冲突 6. ✅ 集成到侧边栏视图,根据登录状态显示不同按钮 7. ✅ 配置网站前端支持插件登录重定向 **最终效果:** - 用户登录后,VSCode 左下角显示"IC Coder 用户" - 侧边栏根据登录状态显示"登录账户"或"开始创作"按钮 - 支持通过账户菜单或命令进行登录/登出操作 --- **文档版本:** 1.0 **最后更新:** 2025-12-29 **作者:** Roe-xin