密码、二维码与 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(架构)
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(执行流程)
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 当前值、认证方法由单选框解释,而不是从页签标题推断:
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
映射三种认证方式
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。
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
WebView 获取 Cookie 的委托边界
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.cn | VPN 目标主机。 |
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.QrCodeAuth | App 指针;返回二维码认证接口 | 构造持有 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.WebviewAuth | App 指针、浏览器路径提示;返回 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。本页没有读取测试文件,无法声称已有自动化测试覆盖上述超时或浏览器异常路径。
Related Links(延伸阅读)
- VPN 认证设置界面与参数映射:进一步定位设置窗口及认证方式转换。
- 二维码 UI 与状态确认:定位扫码交互与状态查询边界。
- WebView UI 适配器:定位浏览器代理委托与 Cookie 返回边界。