Flutter身份验证保护插件auth0_guardian的使用

发布于 1周前 作者 eggper 来自 Flutter

Flutter身份验证保护插件auth0_guardian的使用

介绍

auth0_guardian 是一个社区开发的插件,封装了Auth0的Guardian SDK,支持在Flutter应用中方便地集成多因素身份验证(MFA)服务。Guardian是Auth0提供的多因素认证服务,能够为用户提供简单且安全的MFA实现。

Auth0是一个身份验证代理,支持社交身份提供商(如Google、Facebook等)以及企业身份提供商(如Active Directory、LDAP、Google Apps和Salesforce等)。通过这个SDK,你可以在自己的应用中集成Auth0的Guardian多因素服务,将应用本身作为第二因素,让用户从你的应用中享受到无摩擦的多因素认证体验。

入门指南

要求
平台 版本要求
Flutter SDK 3.3.0+,Dart 3.4.3+
Android API 23+,Java 8+
iOS iOS 13+,Swift 5.0+
安装

要使用auth0_guardian插件,首先需要将其添加到你的项目中:

flutter pub add auth0_guardian
准备工作

在使用此SDK之前,你需要为你的Auth0租户配置Guardian服务,并提供你自己的推送通知凭证(如Firebase或APNs)。具体配置方法可以参考官方文档

此外,你还需要在应用中配置一个通知系统(如Firebase或其他推送服务)。

功能

功能 Android iOS
注册设备
删除设备(注销)
接受请求
拒绝请求
更新设备

使用示例

以下是一个完整的示例代码,展示了如何在Flutter应用中使用auth0_guardian插件进行设备注册、接收和处理登录请求等功能。

import 'dart:developer';

import 'package:auth0_guardian/auth0_guardian.dart';
import 'package:auth0_guardian_example/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import 'package:push/push.dart' as push;

/// 测试用的租户URL
const kTenantUrl = "https://<YOUR_TENANT>/appliance-mfa";

void main() {
  runApp(const GuardianExampleApp());
}

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

  [@override](/user/override)
  State<GuardianExampleApp> createState() => _GuardianExampleAppState();
}

class _GuardianExampleAppState extends State<GuardianExampleApp> {
  /// Auth0 Guardian实例
  final guardian = Guardian(tenantUrl: kTenantUrl);

  /// 设备的通知令牌
  String? notificationToken;

  /// 注册URI
  String? enrollUri;

  /// 已注册的设备
  EnrolledDevice? enrolledDevice;

  /// 存储最新的通知消息
  push.RemoteMessage? latestNotification;

  /// 获取设备的通知令牌
  void setNotificationToken() async {
    // 初始化Firebase
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    // 请求推送通知权限
    await FirebaseMessaging.instance.requestPermission();

    // 获取APNs令牌(iOS)
    final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
    if (apnsToken != null) {
      setState(() => notificationToken = apnsToken);
    } else {
      // 获取Firebase令牌(Android)
      final token = await FirebaseMessaging.instance.getToken();
      setState(() => notificationToken = token);
    }
  }

  /// 扫描二维码获取注册URI
  void scanBarcode() {
    FlutterBarcodeScanner.scanBarcode(
      "#ff6666",
      '取消',
      false,
      ScanMode.QR,
    ).then((qr) {
      setState(() => enrollUri = Uri.decodeFull(qr));
    });
  }

  /// 注册设备
  void enroll(BuildContext context) async {
    final messenger = ScaffoldMessenger.of(context);
    try {
      // 使用注册URI和通知令牌进行设备注册
      final EnrolledDevice device = await guardian.enroll(
        usingUri: enrollUri!,
        notificationToken: notificationToken!,
      );
      setState(() => enrolledDevice = device);
      log(device.toString());
    } catch (e, st) {
      if (kDebugMode) {
        print(e);
        print(st);
      }
      messenger.showSnackBar(SnackBar(content: Text('错误: $e')));
    }
  }

  /// 删除已注册的设备
  void deleteDevice(BuildContext context, EnrolledDevice device) async {
    final messenger = ScaffoldMessenger.of(context);
    try {
      // 删除设备
      final result = await guardian.deleteDevice(device);
      if (result) setState(() => enrolledDevice = null);
    } catch (e, st) {
      if (kDebugMode) {
        print(e);
        print(st);
      }
      messenger.showSnackBar(SnackBar(content: Text('错误: $e')));
    }
  }

  /// 更新已注册的设备(仅限iOS)
  void updateDevice(BuildContext context, EnrolledDevice device) async {
    final messenger = ScaffoldMessenger.of(context);
    try {
      // 更新设备名称
      await guardian.updateDevice(
        device,
        name: '更新后的名称',
        notificationToken: notificationToken,
        localIdentifier: null,
      );
    } catch (e, st) {
      if (kDebugMode) {
        print(e);
        print(st);
      }
      messenger.showSnackBar(SnackBar(content: Text('错误: $e')));
    }
  }

  /// 拒绝最新的登录请求
  Future<void> rejectRequest() async {
    final payload = latestNotification?.data;
    if (payload != null) {
      // 拒绝登录请求
      final result = await guardian.rejectRequest(notification: payload);
      if (result) {
        setState(() => latestNotification = null);
      }
    }
  }

  /// 接受最新的登录请求
  Future<void> acceptRequest() async {
    final payload = latestNotification?.data;
    if (payload != null) {
      // 接受登录请求
      await guardian.acceptRequest(notification: payload);
    }
  }

  [@override](/user/override)
  void initState() {
    super.initState();
    // 添加通知监听器
    push.Push.instance.addOnMessage(onNotification);
    push.Push.instance.addOnBackgroundMessage(onNotification);
    // 获取通知令牌
    setNotificationToken();
  }

  /// 处理接收到的通知
  void onNotification(push.RemoteMessage message) async {
    // 检查通知是否包含数据
    if (message.data == null) return;

    // 检查通知是否来自Guardian
    final isValid = await guardian.isGuardianNotification(message.data!);
    if (isValid) {
      log("接收到Guardian通知: ${message.data}");
      setState(() => latestNotification = message);
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('🔑 Auth0 Guardian 示例应用'),
        ),
        body: SizedBox(
          width: MediaQuery.of(context).size.width,
          child: Builder(builder: (context) {
            return Padding(
              padding: const EdgeInsets.all(8.0),
              child: ListView(
                children: [
                  Visibility(
                    visible: notificationToken == null,
                    replacement: _TextLine(
                      label: '通知令牌 (APNs / Firebase)',
                      value: notificationToken ?? '',
                    ),
                    child: ElevatedButton(
                      onPressed: setNotificationToken,
                      child: const Text('设置 (APNs / Firebase) 令牌'),
                    ),
                  ),
                  const Divider(),
                  const SizedBox(height: 4),
                  ElevatedButton(
                    onPressed: scanBarcode,
                    child: const Text('步骤 1: 扫描二维码'),
                  ),
                  const SizedBox(height: 8),
                  Visibility(
                    visible: enrollUri != null,
                    child: _TextLine(
                      label: '注册 URI',
                      value: enrollUri ?? '',
                    ),
                  ),
                  const Divider(),
                  const SizedBox(height: 4),
                  ElevatedButton(
                    onPressed: () => enroll(context),
                    child: const Text('步骤 2: 触发注册! 🚀'),
                  ),
                  const SizedBox(height: 8),
                  Visibility(
                    visible: enrolledDevice != null,
                    child: _TextLine(
                      label: '已注册的设备',
                      value: enrolledDevice?.id ?? '',
                    ),
                  ),
                  enrolledDevice != null
                      ? Column(
                          children: [
                            const SizedBox(height: 8),
                            ElevatedButton(
                              onPressed: () => deleteDevice(
                                context,
                                enrolledDevice!,
                              ),
                              child: Text(
                                '删除设备 ${enrolledDevice?.id}',
                              ),
                            ),
                            const SizedBox(height: 8),
                            ElevatedButton(
                              onPressed: () => updateDevice(
                                context,
                                enrolledDevice!,
                              ),
                              child: const Text(
                                '更新设备 (仅限iOS)',
                              ),
                            ),
                          ],
                        )
                      : Container(),
                  const Divider(),
                  const SizedBox(height: 4),
                  const Text(
                    '步骤 3: 等待通知',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      decoration: TextDecoration.underline,
                    ),
                  ),
                  if (latestNotification != null)
                    Column(
                      children: [
                        const SizedBox(height: 4),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            ElevatedButton(
                              onPressed: acceptRequest,
                              child: const Text('接受'),
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton(
                              onPressed: rejectRequest,
                              child: const Text('拒绝'),
                            ),
                          ],
                        ),
                        const SizedBox(height: 8),
                        Text(latestNotification?.data.toString() ?? ''),
                      ],
                    )
                ],
              ),
            );
          }),
        ),
      ),
    );
  }
}

class _TextLine extends StatelessWidget {
  const _TextLine({
    required this.label,
    required this.value,
  });

  /// 文本标签
  final String label;

  /// 文本值
  final String value;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '$label: ',
          style: const TextStyle(
            fontWeight: FontWeight.bold,
            decoration: TextDecoration.underline,
          ),
        ),
        const SizedBox(height: 4),
        Text(value)
      ],
    );
  }
}

更多关于Flutter身份验证保护插件auth0_guardian的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter身份验证保护插件auth0_guardian的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用auth0_guardian插件来实现身份验证保护的示例代码。auth0_guardian通常用于增强身份验证流程,通过添加第二层保护(例如,通过短信或电子邮件发送一次性密码)。

首先,确保你已经在pubspec.yaml文件中添加了auth0_guardian依赖:

dependencies:
  flutter:
    sdk: flutter
  auth0_guardian: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

配置Auth0

在使用auth0_guardian之前,你需要在Auth0平台上进行一些配置,包括创建一个应用并启用Guardian。这些步骤在Auth0的官方文档中会有详细的说明。

Flutter代码示例

以下是一个基本的Flutter应用示例,展示了如何使用auth0_guardian进行身份验证保护:

import 'package:flutter/material.dart';
import 'package:auth0_guardian/auth0_guardian.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Auth0 Guardian Example'),
        ),
        body: Center(
          child: GuardianExample(),
        ),
      ),
    );
  }
}

class GuardianExample extends StatefulWidget {
  @override
  _GuardianExampleState createState() => _GuardianExampleState();
}

class _GuardianExampleState extends State<GuardianExample> {
  final Auth0Guardian _auth0Guardian = Auth0Guardian();
  String? _enrollmentId;
  String? _oneTimePassword;

  void _startEnrollment() async {
    try {
      // 替换为你的Auth0域名和客户端ID
      String auth0Domain = 'your-auth0-domain.com';
      String clientId = 'your-client-id';
      String userId = 'user-id-or-email';  // 要保护的用户ID或电子邮件
      
      // 开始注册流程
      String? enrollmentId = await _auth0Guardian.startEnrollment(
        auth0Domain: auth0Domain,
        clientId: clientId,
        userId: userId,
        channel: 'sms',  // 或者 'email'
      );

      if (enrollmentId != null) {
        setState(() {
          _enrollmentId = enrollmentId;
        });
      }
    } catch (e) {
      print('Enrollment error: $e');
    }
  }

  void _verifyEnrollment() async {
    try {
      // 替换为你的Auth0域名和客户端ID
      String auth0Domain = 'your-auth0-domain.com';
      String clientId = 'your-client-id';
      String? otp = _oneTimePassword;  // 用户输入的一次性密码

      // 验证注册
      bool isVerified = await _auth0Guardian.verifyEnrollment(
        auth0Domain: auth0Domain,
        clientId: clientId,
        enrollmentId: _enrollmentId!,
        otp: otp!,
      );

      if (isVerified) {
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('Success'),
            content: Text('Enrollment verified successfully!'),
            actions: <Widget>[
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text('OK'),
              ),
            ],
          ),
        );
      } else {
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('Error'),
            content: Text('Failed to verify enrollment.'),
            actions: <Widget>[
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text('OK'),
              ),
            ],
          ),
        );
      }
    } catch (e) {
      print('Verification error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        ElevatedButton(
          onPressed: _startEnrollment,
          child: Text('Start Enrollment'),
        ),
        SizedBox(height: 20),
        TextField(
          decoration: InputDecoration(labelText: 'Enter OTP'),
          onChanged: (value) {
            setState(() {
              _oneTimePassword = value;
            });
          },
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: _verifyEnrollment,
          child: Text('Verify Enrollment'),
        ),
      ],
    );
  }
}

注意事项

  1. 替换占位符:确保将your-auth0-domain.comyour-client-iduser-id-or-email替换为你的实际Auth0域名、客户端ID和用户ID或电子邮件。

  2. 错误处理:在实际应用中,应添加更详细的错误处理和用户反馈。

  3. 安全性:不要在客户端代码中硬编码敏感信息,如Auth0的客户端密钥或用户密码。

  4. UI/UX:根据实际需求调整UI/UX设计,确保用户体验流畅。

这个示例展示了如何使用auth0_guardian插件启动注册流程并验证一次性密码。如果你需要更高级的功能,请参考Auth0的官方文档和auth0_guardian插件的README文件。

回到顶部