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

在这里插入图片描述

前言

在现代 App 开发中,对接第三方认证(如 Google、GitHub 登录)是不可或缺的功能。OAuth 2.0 协议虽然标准,但由于涉及复杂的认证流、Token 刷新和存储,手动实现极易出错。

Dart 官方提供的 oauth2 库封装了完整的客户端认证逻辑,支持多种授权模式,并能自动处理 Access Token 的过期刷新。本文将演示如何在 OpenHarmony 应用中优雅地集成 oauth2,实现安全的身份认证。

一、oauth2 简介

1.1 核心功能

  • 多种授权模式:支持 Authorization Code, Client Credentials, Resource Owner Password 等。
  • 自动刷新:一旦 Token 过期,自动使用 Refresh Token 获取新凭证。
  • 持久化:方便地序列化和反序列化 Credentials 对象,以便存储。

1.2 OpenHarmony 适配说明

oauth2 是纯 Dart 实现,通过 http 包进行网络通信。因此在 OpenHarmony 上运行时,只需确保网络权限配置正确即可。

认证服务器 (GitHub/Google) 系统浏览器 OpenHarmony 应用 用户 认证服务器 (GitHub/Google) 系统浏览器 OpenHarmony 应用 用户 点击登录 打开授权 URL 用户确认授权 重定向并携带 ?code=... 通过 Deep Link 回调应用 使用 code 交换访问令牌 (Token) 返回 Access & Refresh Token 持久化保存凭证

二、配置与准备

2.1 添加依赖

dependencies:
  oauth2: ^2.0.5
  url_launcher: ^6.3.0 # 用于打开系统浏览器进行用户授权
  http: ^1.2.0

dependency_overrides:
  url_launcher_ohos:
    git:
      url: https://atomgit.com/openharmony-sig/flutter_packages.git
      path: packages/url_launcher/url_launcher_ohos

2.2 网络权限

module.json5 中添加网络权限:

"requestPermissions": [
   { "name": "ohos.permission.INTERNET" }
]

三、核心授权模式与示例

3.1 示例一:客户端凭据模式 (Client Credentials)

适用于服务器对服务器的通信,不涉及用户交互。

import 'package:oauth2/oauth2.dart' as oauth2;

Future<void> clientCredentialsGrant() async {
  final authorizationEndpoint = Uri.parse("https://example.com/oauth2/token");
  final identifier = "my_client_id";
  final secret = "my_client_secret";

  // 创建客户端
  var client = await oauth2.clientCredentialsGrant(
    authorizationEndpoint,
    identifier,
    secret,
  );

  // 使用 client 发起请求,它会自动带上 Bearer Token
  var response = await client.read(Uri.parse("https://example.com/api/resource"));
  print("资源内容: $response");
  
  // 记得关闭
  client.close();
}

在这里插入图片描述

3.2 示例二:资源所有者密码模式 (Resource Owner Password)

用户直接向客户端提供用户名和密码(仅在高度信任的应用中使用)。

import 'package:oauth2/oauth2.dart' as oauth2;

Future<void> passwordGrant(String username, String password) async {
  final endpoint = Uri.parse("https://example.com/oauth2/token");
  
  try {
    var client = await oauth2.resourceOwnerPasswordGrant(
      endpoint,
      username,
      password,
      identifier: "my_client_id",
      secret: "my_client_secret",
    );
    
    print("登录成功,AccessToken: ${client.credentials.accessToken}");
  } catch (e) {
    print("认证失败: $e");
  }
}

在这里插入图片描述

3.3 示例三:凭证存储与恢复

为了避免用户每次打开 App 都要重新登录,我们需要存储 Credentials。

import 'dart:convert';
import 'package:oauth2/oauth2.dart' as oauth2;
// 假设使用 shared_preferences
// import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveCredentials(oauth2.Client client) async {
  // 序列化
  String json = client.credentials.toJson();
  print("保存凭证: $json");
  // await prefs.setString('oauth_credentials', json);
}

Future<oauth2.Client?> loadCredentials() async {
  // String? json = prefs.getString('oauth_credentials');
  String? json = '{"accessToken": "..."}'; // 模拟读取
  
  if (json != null) {
    var credentials = oauth2.Credentials.fromJson(json);
    return oauth2.Client(credentials, identifier: "client_id", secret: "client_secret");
  }
  return null;
}

在这里插入图片描述

四、完整实战示例:GitHub 授权登录

本示例演示最常用的 Authorization Code Grant 模式。用户点击登录 -> 跳转浏览器授权 -> 回调 App -> 获取 Token。

4.1 准备工作

OpenHarmony 需要处理 Deep Link (App Link) 才能从浏览器跳回应用,这部分配置较为复杂。为简化示例,我们将模拟重定向后的 URL 输入,或者在实际设备上配置 skills 里的 uris

这里展示核心逻辑代码。

4.2 示例代码

import 'package:flutter/material.dart';
import 'package:oauth2/oauth2.dart' as oauth2;
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;

final _authorizationEndpoint = Uri.parse('https://github.com/login/oauth/authorize');
final _tokenEndpoint = Uri.parse('https://github.com/login/oauth/access_token');
final _identifier = 'YOUR_GITHUB_CLIENT_ID';
final _secret = 'YOUR_GITHUB_CLIENT_SECRET';
final _redirectUrl = Uri.parse('my.app://oauth-callback');

void main() {
  runApp(const MaterialApp(home: OAuthLoginPage()));
}

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

  
  State<OAuthLoginPage> createState() => _OAuthLoginPageState();
}

class _OAuthLoginPageState extends State<OAuthLoginPage> {
  oauth2.Client? _client;
  oauth2.AuthorizationCodeGrant? _grant;

  Future<void> _login() async {
    // 1. 创建授权请求管理对象
    _grant = oauth2.AuthorizationCodeGrant(
      _identifier,
      _authorizationEndpoint,
      _tokenEndpoint,
      secret: _secret,
      httpClient: _JsonAcceptingHttpClient(), // GitHub 需要 Accept: application/json
    );

    // 2. 获取授权 URL
    var authorizationUrl = _grant!.getAuthorizationUrl(_redirectUrl, scopes: ['read:user']);

    // 3. 打开浏览器 (OpenHarmony 上 url_launcher 可正常工作)
    if (await canLaunchUrl(authorizationUrl)) {
      await launchUrl(authorizationUrl, mode: LaunchMode.externalApplication);
    }
    
    // ⚠️ 注意:真实场景中,App 需要配置 Deep Link 监听
    // 当浏览器跳转回 my.app://oauth-callback?code=... 时,捕获该 URL
    // 这里为了演示,我们模拟手动输入回调 URL
    _showManualInputCallback();
  }

  void _handleCallback(Uri responseUrl) async {
    try {
      // 4. 交换 Token
      var client = await _grant!.handleAuthorizationResponse(responseUrl.queryParameters);
      setState(() {
        _client = client;
      });
      _getUserInfo();
    } catch (e) {
      print("授权失败: $e");
    }
  }

  Future<void> _getUserInfo() async {
    if (_client == null) return;
    try {
      var response = await _client!.read(Uri.parse('https://api.github.com/user'));
      print("用户信息: $response");
      // 显示用户信息...
    } catch (e) {
      print("获取信息失败: $e");
    }
  }

  void _showManualInputCallback() {
      showDialog(
        context: context, 
        builder: (ctx) {
          final controller = TextEditingController();
          return AlertDialog(
            title: const Text("模拟回调"),
            content: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text("在真实 App 中,这一步由 Deep Links 自动完成。现在请手动粘贴浏览器地址栏的回调 URL:"),
                TextField(controller: controller, decoration: const InputDecoration(hintText: "my.app://...?code=...")),
              ],
            ),
            actions: [
              TextButton(
                onPressed: () {
                   Navigator.pop(ctx);
                   if (controller.text.isNotEmpty) {
                     _handleCallback(Uri.parse(controller.text));
                   }
                }, 
                child: const Text("确定")
              )
            ],
          );
        }
      );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('OAuth2 GitHub 登录')),
      body: Center(
        child: _client == null
            ? ElevatedButton(
                onPressed: _login,
                child: const Text('Login with GitHub'),
              )
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                   const Text("登录成功!"),
                   const SizedBox(height: 20),
                   const Text("AccessToken 已获取"),
                   ElevatedButton(
                     onPressed: _getUserInfo, 
                     child: const Text("刷新用户信息")
                   ),
                ],
              ),
      ),
    );
  }
}

// 辅助类:GitHub API 默认返回 XML 格式 Token,需强制要求 JSON
class _JsonAcceptingHttpClient extends http.BaseClient {
  final _httpClient = http.Client();
  
  Future<http.StreamedResponse> send(http.BaseRequest request) {
    request.headers['Accept'] = 'application/json';
    return _httpClient.send(request);
  }
}

在这里插入图片描述

五、总结

oauth2 库极大简化了在 OpenHarmony 上实现标准认证流程的工作。结合 url_launcher 和 deep links,可以构建流畅的第三方登录体验。

最佳实践

  1. 安全存储:不要将 client_secret 硬编码在代码中,建议通过混淆或服务端代理获取。
  2. 凭证管理:务必持久化保存 Credentials,并在 App 启动时尝试恢复,避免用户重复登录。
  3. OpenHarmony Deep Link:深入研究 module.json5 中的 uris 配置,确保浏览器能正确唤起 App。
Logo

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

更多推荐