前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

要理解 flutter_web_auth 为什么这样设计,得先搞懂 OAuth2 协议。很多开发者用 OAuth2 登录用了好几年,但对协议本身一知半解——知道要拿 code 换 token,但不清楚为什么要这么绕。这篇把 OAuth2 的授权码模式从头讲一遍,然后对应到 flutter_web_auth 的每个参数上。

一、为什么需要 OAuth2

1.1 没有 OAuth 的时代

用户 → 把用户名密码给第三方 App → App 直接登录用户的账号

问题很明显:

  • 第三方 App 拿到了用户的密码
  • 用户无法控制 App 的权限范围
  • 用户改密码后所有第三方 App 都失效

1.2 OAuth2 的解决方案

用户 → 在授权服务器上登录 → 授权服务器给 App 一个临时令牌 → App 用令牌访问资源

请添加图片描述

二、OAuth2 四种授权模式

2.1 模式概览

请添加图片描述

2.2 为什么授权码模式最安全

隐式模式:浏览器直接拿到 access_token → token 暴露在 URL 中
授权码模式:浏览器只拿到 code → code 在后端换 token → token 不经过浏览器

💡 授权码模式多了一步"用 code 换 token",看起来多此一举,但这一步让 access_token 永远不会出现在浏览器的地址栏里,大大降低了泄露风险。

2.3 flutter_web_auth 的定位

flutter_web_auth 不限制你用哪种模式。它只负责"打开浏览器"和"接收回调 URL"。但实际使用中,授权码模式是最常见的。

三、授权码流程完整时序

3.1 时序图

┌──────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│ 用户  │     │   App    │     │  浏览器   │     │ 授权服务器 │
└──┬───┘     └────┬─────┘     └────┬─────┘     └────┬─────┘
   │  点击登录    │                │                │
   │ ──────────> │                │                │
   │             │  打开浏览器     │                │
   │             │ ─────────────> │                │
   │             │                │  加载授权页面   │
   │             │                │ ─────────────> │
   │  输入密码    │                │                │
   │ ──────────────────────────> │                │
   │             │                │  授权成功       │
   │             │                │ <───────────── │
   │             │                │  重定向到       │
   │             │                │  scheme://      │
   │             │                │  ?code=abc123   │
   │             │  深度链接回调   │                │
   │             │ <───────────── │                │
   │             │                                 │
   │             │  用 code 换 token                │
   │             │ ──────────────────────────────> │
   │             │  返回 access_token               │
   │             │ <────────────────────────────── │
   │  登录成功    │                                 │
   │ <────────── │                                 │

3.2 每一步的参数

步骤 参数 示例
1. 构造授权 URL client_id, redirect_uri, scope, state https://auth.example.com/authorize?client_id=xxx&redirect_uri=myapp://&scope=email&state=random
2. 打开浏览器 url flutter_web_auth 的 url 参数
3. 用户授权 用户名, 密码 在浏览器中完成
4. 重定向回调 code, state myapp://callback?code=abc123&state=random
5. 换取 token code, client_secret POST 请求到 token endpoint

3.3 flutter_web_auth 参与的步骤

// 步骤 1:构造授权 URL(开发者自己做)
final url = Uri.https('accounts.google.com', '/o/oauth2/v2/auth', {
  'response_type': 'code',
  'client_id': googleClientId,
  'redirect_uri': '$callbackUrlScheme:/',
  'scope': 'email',
});

// 步骤 2-4:flutter_web_auth 负责
final result = await FlutterWebAuth.authenticate(
  url: url.toString(),
  callbackUrlScheme: callbackUrlScheme,
);

// 步骤 4:解析回调 URL(开发者自己做)
final code = Uri.parse(result).queryParameters['code'];

// 步骤 5:换取 token(开发者自己做)
final response = await http.post(tokenEndpoint, body: {
  'grant_type': 'authorization_code',
  'code': code,
  'client_id': clientId,
  'client_secret': clientSecret,
});

四、callbackUrlScheme 的作用

4.1 什么是 URL Scheme

myapp://callback?code=abc123
│       │         │
Scheme  Host      Query Parameters

URL Scheme 是 URL 的第一部分,用于标识协议类型。常见的有 httphttpsftp。自定义 Scheme 如 myapp 可以用来唤起特定的 App。

4.2 RFC 3986 规范

static RegExp _schemeRegExp = new RegExp(r"^[a-z][a-z0-9+.-]*$");

合法的 Scheme 必须:

  1. 以小写字母开头
  2. 后续只能包含小写字母、数字、+.-
Scheme 合法 原因
myapp 纯小写字母
com.example.app 包含点号
my-app 包含连字符
MyApp 包含大写字母
my_app 包含下划线
1app 以数字开头

📌 这是最常见的坑之一。很多开发者用了下划线或大写字母,导致回调永远收不到。flutter_web_auth 在 Dart 层做了正则校验,不合法的 Scheme 会直接抛出 ArgumentError

4.3 在各平台的配置

平台 配置位置 配置方式
Android AndroidManifest.xml <data android:scheme="myapp" />
iOS Info.plist URL Types → URL Schemes
OpenHarmony module.json5 skills → uris → scheme
Web 不需要 通过 postMessage 传递

五、Access Token / Refresh Token / Authorization Code

5.1 三者的关系

Authorization Code → 换取 → Access Token + Refresh Token
                              │                │
                              ↓                ↓
                         访问资源(短期)    刷新 Token(长期)

5.2 生命周期对比

凭证 有效期 用途 安全要求
Authorization Code 几分钟 一次性换 token
Access Token 几小时 访问 API
Refresh Token 几天到几个月 刷新 access token 极高

5.3 flutter_web_auth 只涉及 Authorization Code

// flutter_web_auth 返回的是包含 code 的 URL
final result = await FlutterWebAuth.authenticate(...);
// result = "myapp://callback?code=abc123"

// 后续的 token 交换不是 flutter_web_auth 的职责
final code = Uri.parse(result).queryParameters['code'];

六、隐式模式与授权码模式的对比

6.1 隐式模式

// 隐式模式:浏览器直接返回 token
final url = Uri.https('auth.example.com', '/authorize', {
  'response_type': 'token',  // 注意:是 token 不是 code
  'client_id': clientId,
  'redirect_uri': '$callbackUrlScheme:/',
});

final result = await FlutterWebAuth.authenticate(
  url: url.toString(),
  callbackUrlScheme: callbackUrlScheme,
);

// 直接从 URL fragment 中获取 token
final token = Uri.parse(result).fragment.split('&')
    .firstWhere((e) => e.startsWith('access_token='))
    .split('=')[1];

6.2 两种模式的安全性对比

维度 授权码模式 隐式模式
Token 暴露在 URL 中 ❌ 不会 ✅ 会
需要 client_secret ✅ 是 ❌ 否
支持 Refresh Token ✅ 是 ❌ 否
适合移动端 ✅ 推荐 ⚠️ 不推荐

💡 OAuth 2.1 草案已经废弃了隐式模式。移动端应该始终使用授权码模式 + PKCE。

七、flutter_web_auth 的职责边界

7.1 职责清单

flutter_web_auth 做的事:

  1. 校验 callbackUrlScheme 的合法性
  2. 通过 MethodChannel 调用原生端
  3. 原生端打开系统浏览器
  4. 监听深度链接回调
  5. 把回调 URL 返回给 Dart
  6. 处理用户取消(dangling calls 清理)

flutter_web_auth 不做的事:

  • 构造 OAuth2 授权 URL
  • 管理 client_id / client_secret
  • 用 code 换取 token
  • 存储和刷新 token
  • 实现登录 UI
  • 管理用户会话

7.2 与其他包的配合

// flutter_web_auth:打开浏览器 + 接收回调
final result = await FlutterWebAuth.authenticate(...);

// http:发送 HTTP 请求换 token
final response = await http.post(tokenEndpoint, body: {...});

// flutter_secure_storage:安全存储 token
await storage.write(key: 'access_token', value: token);

// dio:带 token 访问 API
dio.options.headers['Authorization'] = 'Bearer $token';

7.3 为什么不做更多

设计选择 原因
不构造 URL 每个 OAuth 提供商的参数不同
不换 token 需要 client_secret,不应在客户端
不存 token 存储策略因项目而异
不管 UI 认证页面由服务商提供

📌 单一职责原则。flutter_web_auth 只做"浏览器认证桥梁"这一件事,做好这一件事。其他的交给专门的包。

总结

本文讲解了 OAuth2 协议基础和 flutter_web_auth 的定位:

  1. OAuth2 授权码模式:最安全的授权方式,code 不直接暴露 token
  2. callbackUrlScheme:必须符合 RFC 3986,小写字母开头,不能有下划线
  3. flutter_web_auth 的职责:只负责"打开浏览器"和"接收回调"两步
  4. 与其他包配合:http 换 token、flutter_secure_storage 存 token

下一篇我们逐行解析 Dart 层源码——55 行代码里藏了不少精妙的设计。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐