Repository Wiki
genshen/wssocks-plugin-ustb

密码、二维码与 WebView 登录

本页解释客户端 VPN 认证设置界面如何选择密码、二维码和 WebView 方式,以及 Fyne 实现如何将二维码交互和浏览器登录交给 VPN 认证模块。

Purpose and Scope(目的与范围)

范围是 client-ui 中的认证入口、UI 配置到 vpn.UstbVpn 的映射,以及二维码和 WebView 的 UI 适配器。底层密码提交、二维码状态查询协议、浏览器自动化内部行为不在所读取实现范围内;涉及这些细节时仅描述可见调用边界。VPN 连接、代理设置和其他客户端界面应由各自专题解释。该页不是 HTTP 认证协议文档。

Overview(概述)

VpnSettingsUI.OpenVpnSettings 创建 VPN Auth Settings 窗口:通用表单含强制退出、主机加密、VPN 主机及认证方式单选框;三个页签分别提供用户名/密码输入、二维码占位内容和浏览器路径选择。单选框选择决定 getAuthMethodInt 写入的认证枚举,而不是通过切换页签来决定认证方式。LoadSettingsValues 从当前控件读取通用配置和密码凭据。二维码和 WebView 另有实现认证接口的 Fyne 适配器:前者生成 QR 图片并等待用户确认,后者将获取 Cookie 委托给 ChromedpWebview。参见 vpn_settings_ui.go、qr_login_ui.go 与 webview_login_ui.go。

Architecture(架构)

Loading diagram...

Sources: vpn_settings_ui.go, qr_login_ui.go, webview_login_ui.go

设置窗口只持有控件,LoadSettingsValues 将当前值写入调用者传入的 vpn.UstbVpn;独立的二维码、浏览器适配器负责认证时的交互。因此设置页的 QR Code Auth 页签当前是 World! 标签,不等于运行时展示二维码的窗口。设置页 与 二维码窗口 分处不同路径。

设置界面与认证方式映射

OpenVpnSettings(wssApp *fyne.App, pref fyne.Preferences) 初始化表单:newCheckbox("", true, nil) 创建两个默认勾选的选项;主机文本初值 n.ustb.edu.cn,用户名和密码为空,密码 Entry.Password 为 true。认证单选框列出密码、二维码、WebView 三种文本选项,回调目前只有 todo,不随选项即时改变页签。浏览器路径输入框初始禁用,选择文件的按钮通过 dialog.NewFileOpen 获取 reader.URI().Path() 并写到输入框;错误或取消时直接返回。关闭窗口时调用 saveVPNPreference,打开时调用 loadVpnPreference;这两个函数的内部逻辑未在本页所读文件中验证,不能据此推断密码是否持久化。参见 vpn_settings_ui.go。

LoadSettingsValues(values *vpn.UstbVpn) 则直接把 UI 控件的当前状态写到 ForceLogout、HostEncrypt、TargetVpn、AuthMethod 和 PasswdAuth。这里没有按认证方式过滤用户名或密码:即使选择二维码/WebView,也会填充 PasswdAuth。getAuthMethodInt 对已知密码和二维码文本分别返回对应常量,其余选择均落入 WebView 分支;不能把这一兜底分支解释为输入验证。参见 vpn_settings_ui.go。

Core Flow(执行流程)

Loading diagram...

Sources: vpn_settings_ui.go, qr_login_ui.go, webview_login_ui.go

图中的 opt 表示适配器各自被调用时的局部流程,而非已验证的 LoadSettingsValues 自动调用适配器;所读源码没有展示调用者如何根据 AuthMethod 分派。

二维码交互的两个等待阶段

NewQrCodeAuth 返回指向 FyneQrCodeAuth 的 qrcode2.QrCodeAuth 接口值。ShowQrCodeAndWait 先用 qr.GenQrCodeContent() 生成内容,再以 qrcode.Medium、256 像素编码为 PNG,转换为 Fyne 图片;Finish 按钮向容量 1 的 scanned 通道发送信号。等待阶段用 context.WithTimeout(context.Background(), 30*time.Second) 选择按钮事件或超时,两个分支均关闭窗口。按钮只表示用户主动结束扫码阶段,并不是认证已成功;接下来 WaitStatus 才调用 qrcode2.WaitQrState(qr.Sid),成功后调用 qrcode2.RedirectToLogin。成功路径返回 nil, nil,并非直接返回更新后的 Cookie。参见 qr_login_ui.go。

WebView 委托

NewWebviewAuth 构造 FyneWebviewAuth,其中 chromeProxy 被赋值为 &webview.ChromedpWebview{},浏览器路径提示保存到 chromePathHint。GetCookie 在代理指针非空时直接转发 client、loginUrl、chromePathHint;WaitAuthFinished 当前直接返回 nil。所读代码没有展示 Chrome 发现机制、浏览器启动选项或 Cookie 的底层采集算法。参见 webview_login_ui.go。

Usage Examples(源码示例)

从 UI 控件填充 VPN 值

下列实现表明密码输入为 UI 当前值、认证方法由单选框解释,而不是从页签标题推断:

go
1func (v *VpnSettingsUI) LoadSettingsValues(values *vpn.UstbVpn) { 2 values.ForceLogout = v.uiVpnForceLogout.Checked 3 values.HostEncrypt = v.uiVpnHostEncrypt.Checked 4 values.TargetVpn = v.uiVpnHostInput.Text 5 values.AuthMethod = getAuthMethodInt(v.uiVpnAuthMethod) 6 values.PasswdAuth = passwd.UstbVpnPasswdAuth{ 7 Username: v.uiVpnUsername.Text, 8 Password: v.uiVpnPassword.Text, 9 } 10}

Source: vpn_settings_ui.go

映射三种认证方式

go
1func getAuthMethodInt(uiVpnAuthMethod *widget.RadioGroup) int { 2 if uiVpnAuthMethod.Selected == TextVpnAuthMethodPasswd { 3 return vpn.VpnAuthMethodPasswd 4 } else if uiVpnAuthMethod.Selected == TextVpnAuthMethodQrCode { 5 return vpn.VpnAuthMethodQRCode 6 } else { 7 return vpn.VpnAuthMethodWebview 8 } 9}

Source: vpn_settings_ui.go

二维码确认后的查询与重定向

WaitStatus 处理扫码后的服务端状态和登录重定向;错误向上返回,成功则返回两个 nil。

go
1func WaitStatus(client *http.Client, cookies []*http.Cookie, qr qrcode2.QrImg) ([]*http.Cookie, error) { 2 // todo: set http cancel, after timeout 3 if state, err := qrcode2.WaitQrState(qr.Sid); err != nil { 4 fmt.Println(err) 5 return nil, err 6 } else { 7 if err = qrcode2.RedirectToLogin(client, cookies, qr.Config.AppID, state, qr.Config.RandToken); err != nil { 8 fmt.Println(err) 9 return nil, err 10 } 11 } 12 13 return nil, nil 14}

Source: qr_login_ui.go

go
1func (w *FyneWebviewAuth) GetCookie(client *http.Client, loginUrl string) ([]*http.Cookie, error) { 2 if w.chromeProxy == nil { 3 return nil, errors.New("Chromedp is not created") 4 } 5 6 // created ui: 7 return w.chromeProxy.ShowWebviewAndSetCookies(client, loginUrl, w.chromePathHint) 8}

Source: webview_login_ui.go

Configuration Options(界面选项)

下表记录本页可验证的界面初始化值,不把它们误写成 fyne.Preferences 最终加载值;loadVpnPreference 在初始化之后执行,可能覆盖这些值,但实际覆盖规则未在所读实现中确认。初始化与加载调用。

UI 控件 / 对应字段类型初始化值作用
uiVpnForceLogout / ForceLogout*widget.Check / 布尔true写入 VPN 配置的强制退出开关。
uiVpnHostEncrypt / HostEncrypt*widget.Check / 布尔true写入主机加密开关。
uiVpnHostInput / TargetVpn*widget.Entry / 字符串n.ustb.edu.cnVPN 目标主机。
uiVpnUsername、uiVpnPassword / PasswdAuth两个 *widget.Entry / 字符串空字符串密码输入框启用 Password: true;内容写入 UstbVpnPasswdAuth。
uiVpnAuthMethod / AuthMethod*widget.RadioGroup / int初始化时未显式选中密码、二维码、WebView 选择映射为 VPN 常量。
uiChromePathContainer*widget.Entry / 字符串空、禁用通过浏览器文件选择按钮填入路径;读取到适配器的具体传递链本页未验证。

选项和值的对应关系见 vpn_settings_ui.go 及 vpn_settings_ui.go。

API Reference(可见接口)

方法(源码签名)参数及结果行为
(*VpnSettingsUI).OpenVpnSettings(wssApp *fyne.App, pref fyne.Preferences)Fyne App 指针、偏好接口;无返回值创建并显示设置窗口;关闭时调用保存偏好的函数。实现
(*VpnSettingsUI).LoadSettingsValues(values *vpn.UstbVpn)可变 VPN 值指针;无返回值将当前 UI 控件读数写入该值。实现
getAuthMethodInt(uiVpnAuthMethod *widget.RadioGroup) int单选框;返回对应 VPN 认证常量非密码、非二维码文本走 WebView 分支。实现
NewQrCodeAuth(app *fyne.App) qrcode2.QrCodeAuthApp 指针;返回二维码认证接口构造持有 App 引用的 Fyne 实现。实现
(*FyneQrCodeAuth).ShowQrCodeAndWait(client *http.Client, cookies []*http.Cookie, qr qrcode2.QrImg) ([]*http.Cookie, error)HTTP 客户端、已有 Cookie、二维码信息;返回 Cookie 切片及错误编码图片并显示窗口;30 秒内点击 Finish 则转入 WaitStatus,否则报超时。实现
WaitStatus(client *http.Client, cookies []*http.Cookie, qr qrcode2.QrImg) ([]*http.Cookie, error)同上;成功为 (nil, nil)等待扫码状态,然后用应用 ID、状态和随机令牌重定向登录;错误原样返回。实现
NewWebviewAuth(app *fyne.App, chromePathInSettings string) webview.WebviewAuthApp 指针、浏览器路径提示;返回 WebView 认证接口创建 Fyne 适配器及 ChromedpWebview 实例。实现
(*FyneWebviewAuth).GetCookie(client *http.Client, loginUrl string) ([]*http.Cookie, error)HTTP 客户端、登录 URL;返回 Cookie 切片和错误非空代理时委托浏览器实现;空代理报错。实现
(*FyneWebviewAuth).WaitAuthFinished() error无参数;总是返回 nil当前未执行额外等待。实现

Failure Modes, Edge Cases & Concurrency(错误、边界与并发)

  • 二维码编码错误会立即返回 (nil, err),尚未打开窗口;30 秒内没有点击 Finish 则关闭窗口并返回 scan QR code canceled due to timeout。二维码实现
  • Finish 之后调用 WaitStatus;查询状态或重定向失败都会打印错误并返回它。这里的 30 秒 context 只约束点击确认前的等待;WaitStatus 自身标注了待添加 HTTP 取消逻辑,不能声称后续请求也受相同超时约束。二维码状态查询
  • scanned 是容量为 1 的 chan bool,按钮回调发送信号,主等待逻辑在 select 中接收。超时后窗口关闭;源码未展示按钮重复点击的额外保护,不应将 Finish 视为幂等网络操作。二维码窗口
  • 浏览器代理为 nil 时 GetCookie 返回 Chromedp is not created;由 NewWebviewAuth 正常创建的实例会初始化代理。底层 ShowWebviewAndSetCookies 的错误直接向外传播。WebView 适配器
  • 文件选择回调遇到错误或没有 reader 就退出而不更新路径;设置页的二维码页签是占位标签,选中该页签不等于已经完成扫码。文件选择与页签

Performance, Operations & Extension Points(运行与扩展)

二维码 PNG 每次 ShowQrCodeAndWait 被调用时现场生成,尺寸固定为 256,纠错等级 qrcode.Medium;所读 UI 实现没有二维码图片缓存。扫码确认窗口具有显式 30 秒期限,但后续状态查询无本地取消机制。浏览器路径由 NewWebviewAuth 参数注入,实际浏览器行为由 webview.ChromedpWebview 提供;若需改变交互入口,可在相应认证接口的 UI 实现处扩展,而不要把页签选择当作认证结果。参见 qr_login_ui.go、webview_login_ui.go。本页没有读取测试文件,无法声称已有自动化测试覆盖上述超时或浏览器异常路径。