前言

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

前面 15 篇讲了原理和源码,这篇来点实际的——从零搭建一个示例应用,接入 GitHub OAuth 登录,在 OpenHarmony 真机上跑通完整流程。代码可以直接复制到项目里用。

一、example 项目结构

1.1 目录结构

example/
├── lib/
│   └── main.dart              # 示例应用主文件
├── ohos/
│   └── entry/
│       └── src/
│           └── main/
│               ├── ets/
│               │   └── entryability/
│               │       └── EntryAbility.ets    # 宿主 Ability
│               └── module.json5                # 宿主配置
├── android/
│   └── app/
│       └── src/
│           └── main/
│               └── AndroidManifest.xml
└── pubspec.yaml

1.2 关键文件

文件 作用 需要修改
main.dart 示例 UI 和认证逻辑
EntryAbility.ets 深度链接回调转发
module.json5 skills 和权限配置
AndroidManifest.xml Intent Filter 配置

二、构造 OAuth2 授权 URL

2.1 GitHub OAuth 参数

参数 说明
client_id 在 GitHub 注册获取 应用标识
redirect_uri myapp://callback 回调地址
scope read:user 请求的权限
state 随机字符串 防 CSRF

2.2 构造 URL

import 'dart:math';

String _generateState() {
  final random = Random.secure();
  return List.generate(32, (_) => random.nextInt(16).toRadixString(16)).join();
}

final state = _generateState();
final authUrl = Uri.https('github.com', '/login/oauth/authorize', {
  'client_id': 'your_github_client_id',
  'redirect_uri': 'myapp://callback',
  'scope': 'read:user',
  'state': state,
});

2.3 Google OAuth 参数

final googleAuthUrl = Uri.https('accounts.google.com', '/o/oauth2/v2/auth', {
  'response_type': 'code',
  'client_id': 'your_google_client_id.apps.googleusercontent.com',
  'redirect_uri': 'com.googleusercontent.apps.your-client-id:/',
  'scope': 'email profile',
  'state': state,
});

📌 Google OAuth 的 callbackUrlScheme 格式比较特殊com.googleusercontent.apps.{client_id}。注意这个 Scheme 全是小写字母和点号,符合 RFC 3986。

三、callbackUrlScheme 在宿主应用中的配置

3.1 OpenHarmony module.json5

{
  "module": {
    "name": "entry",
    "type": "entry",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "launchType": "singleton",
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["action.system.home"]
          },
          {
            "entities": ["entity.system.browsable"],
            "actions": ["ohos.want.action.viewData"],
            "uris": [{ "scheme": "myapp" }]
          }
        ]
      }
    ],
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
  }
}

3.2 Android AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application>
    <activity android:name="com.linusu.flutter_web_auth.CallbackActivity"
              android:exported="true">
      <intent-filter android:label="flutter_web_auth">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myapp" />
      </intent-filter>
    </activity>
  </application>
</manifest>

3.3 配置检查清单

  • OpenHarmony: skills 中 uris.scheme = “myapp”
  • OpenHarmony: launchType = “singleton”
  • OpenHarmony: requestPermissions 包含 INTERNET
  • Android: CallbackActivity 的 data.scheme = “myapp”
  • 两端的 Scheme 与 Dart 代码中的 callbackUrlScheme 一致

四、EntryAbility.ets 集成代码

4.1 完整代码

import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
import { AbilityConstant, Want } from '@kit.AbilityKit';
import FlutterWebAuthPlugin from 'flutter_web_auth';

export default class EntryAbility extends FlutterAbility {
  configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine);
    GeneratedPluginRegistrant.registerWith(flutterEngine);
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    super.onNewWant(want, launchParam);
    FlutterWebAuthPlugin.onNewWant(want);
  }

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    super.onCreate(want, launchParam);
    FlutterWebAuthPlugin.onNewWant(want);
  }
}

4.2 关键点

要点 说明
import FlutterWebAuthPlugin 从包名导入,不是文件路径
super.onNewWant 必须调用,不能遗漏
onCreate 也处理 覆盖冷启动场景

五、完整示例应用代码

5.1 main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';
import 'dart:math';
import 'dart:convert';
import 'package:http/http.dart' as http;

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Web Auth Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const AuthPage(),
    );
  }
}

class AuthPage extends StatefulWidget {
  const AuthPage({super.key});

  
  State<AuthPage> createState() => _AuthPageState();
}

class _AuthPageState extends State<AuthPage> {
  String _status = '未登录';
  String _userInfo = '';
  bool _isLoading = false;

  String _generateState() {
    final random = Random.secure();
    return List.generate(32, (_) => random.nextInt(16).toRadixString(16)).join();
  }

  Future<void> _loginWithGitHub() async {
    setState(() {
      _isLoading = true;
      _status = '正在认证...';
    });

    final state = _generateState();
    const clientId = 'your_github_client_id';
    const callbackScheme = 'myapp';

    final authUrl = Uri.https('github.com', '/login/oauth/authorize', {
      'client_id': clientId,
      'redirect_uri': '$callbackScheme://callback',
      'scope': 'read:user',
      'state': state,
    });

    try {
      final result = await FlutterWebAuth.authenticate(
        url: authUrl.toString(),
        callbackUrlScheme: callbackScheme,
      );

      final returnedState = Uri.parse(result).queryParameters['state'];
      if (returnedState != state) {
        throw Exception('State 不匹配,可能存在 CSRF 攻击');
      }

      final code = Uri.parse(result).queryParameters['code'];
      setState(() {
        _status = '获取到授权码';
        _userInfo = 'code: ${code?.substring(0, 10)}...';
      });

    } on PlatformException catch (e) {
      setState(() {
        if (e.code == 'CANCELED') {
          _status = '用户取消了登录';
        } else {
          _status = '登录失败: ${e.code}';
          _userInfo = e.message ?? '';
        }
      });
    } catch (e) {
      setState(() {
        _status = '错误: $e';
      });
    } finally {
      setState(() => _isLoading = false);
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter Web Auth Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_status, style: const TextStyle(fontSize: 18)),
            if (_userInfo.isNotEmpty)
              Padding(
                padding: const EdgeInsets.all(16),
                child: Text(_userInfo, style: const TextStyle(fontSize: 14, color: Colors.grey)),
              ),
            const SizedBox(height: 32),
            ElevatedButton(
              onPressed: _isLoading ? null : _loginWithGitHub,
              child: _isLoading
                  ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
                  : const Text('使用 GitHub 登录'),
            ),
          ],
        ),
      ),
    );
  }
}

5.2 UI 状态流转

状态 显示 按钮
初始 “未登录” 可点击
认证中 “正在认证…” 禁用 + loading
成功 “获取到授权码” + code 可点击
取消 “用户取消了登录” 可点击
失败 “登录失败: {code}” 可点击

六、GitHub OAuth 完整接入

6.1 注册 GitHub OAuth App

  1. 打开 https://github.com/settings/developers
  2. 点击 “New OAuth App”
  3. 填写信息:
字段
Application name My Flutter App
Homepage URL https://example.com
Authorization callback URL myapp://callback
  1. 获取 Client ID 和 Client Secret

6.2 用 code 换 token

Future<String> _exchangeCodeForToken(String code) async {
  final response = await http.post(
    Uri.https('github.com', '/login/oauth/access_token'),
    headers: {'Accept': 'application/json'},
    body: {
      'client_id': 'your_client_id',
      'client_secret': 'your_client_secret',  // ⚠️ 不应在客户端
      'code': code,
    },
  );
  
  final data = jsonDecode(response.body);
  return data['access_token'] as String;
}

⚠️ client_secret 不应该放在客户端代码中。生产环境应该通过后端服务器来换取 token。这里只是演示用途。

6.3 获取用户信息

Future<Map<String, dynamic>> _getUserInfo(String token) async {
  final response = await http.get(
    Uri.https('api.github.com', '/user'),
    headers: {'Authorization': 'Bearer $token'},
  );
  return jsonDecode(response.body);
}

七、在 OpenHarmony 真机上运行

7.1 环境要求

要求 版本
Flutter-OHOS SDK 3.35.7-dev
DevEco Studio 6.0.2 Release
OpenHarmony SDK API 20
设备 ROM 6.0.0.130 SP8

7.2 运行步骤

# 1. 获取依赖
flutter pub get

# 2. 连接设备
hdc list targets

# 3. 运行
flutter run -d <device_id>

7.3 验证流程

  1. 点击"使用 GitHub 登录"按钮
  2. 系统浏览器打开 GitHub 登录页面
  3. 输入 GitHub 账号密码(或使用已登录的会话)
  4. 点击"Authorize"授权
  5. 浏览器重定向到 myapp://callback?code=xxx&state=xxx
  6. App 自动回到前台,显示授权码

7.4 常见运行问题

问题 原因 解决
浏览器不打开 INTERNET 权限未声明 检查 module.json5
回调不触发 skills 配置错误 检查 Scheme 拼写
App 没有回到前台 launchType 不是 singleton 修改为 singleton
onNewWant 不调用 EntryAbility 未集成 添加 onNewWant 方法

总结

本文完成了 flutter_web_auth 的示例应用开发:

  1. 项目结构:Dart 代码 + 宿主配置 + EntryAbility 集成
  2. OAuth2 URL 构造:client_id + redirect_uri + scope + state
  3. 宿主配置:module.json5 的 skills + INTERNET 权限
  4. 完整 UI:状态管理 + loading + 错误处理
  5. 真机运行:环境要求 + 运行步骤 + 常见问题

下一篇我们做四平台认证机制的横向对比。

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


相关资源:

在这里插入图片描述

flutter_web_auth 示例应用认证流程

Logo

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

更多推荐