Flutter自动登录插件autologin_darwin的使用

Flutter自动登录插件autologin_darwin的使用

该插件实现了iOS和macOS平台上的自动登录功能。你可以通过以下方式来使用它。

使用

此包是被推荐使用的,这意味着你可以在项目中直接使用autologin。当你这样做的时候,此包会自动包含在你的应用中,所以你不需要在pubspec.yaml文件中添加它。

但是,如果你导入此包以直接使用其任何API,则应像往常一样将其添加到pubspec.yaml文件中。

安装

为了存储凭证,此插件使用了共享网络凭据。为了使这正常工作,你需要设置关联域授权。主要文档可以在Apple开发者网站上找到。至少apple-app-site-association文件必须在https://<your-domain>/.well-known/apple-app-site-association可访问,并且必须包含如下内容:

{
  "webcredentials": {
    "apps": [
      "<your-team-id>.<your-bundle-id>"
    ]
  }
}

你的团队ID可以在ios/Runner.xcodeproj/project.pbxproj中找到,例如,在DEVELOPMENT_TEAM键下查找;而包ID可以在PRODUCT_BUNDLE_IDENTIFIER键下查找。Apple会用其CDN缓存对上述文件的请求,但你可以在这里检查缓存值:https://app-site-association.cdn-apple.com/a/v1/<your-domain>

如果你还没有设置你的应用,请检查Apple开发者资源

为了在iOS和macOS上使用零触登录,你需要添加iCloud能力并在Xcode项目中配置iCloud键值存储。以下是逐步指南:

  1. 打开你的Xcode项目。
  2. 在项目导航器中选择你的项目以打开项目设置。
  3. 在“目标”下选择你的目标。
  4. 转到“签名与功能”选项卡。
  5. 点击“+ 功能”按钮。
  6. 滑动并选择“iCloud”。
  7. 在iCloud部分中,勾选“键值存储”。

示例代码

以下是一个完整的示例代码,展示了如何在Flutter应用中使用autologin插件。

import 'dart:async';

import 'package:autologin/autologin.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

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

/// 这个框架只是为了确保DemoPage有一个可以显示snackbar的上下文
class DemoFrame extends StatelessWidget {
  const DemoFrame({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Autologin-Plugin 示例应用'),
        ),
        body: const SingleChildScrollView(
          padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24),
          child: Align(
            child: SizedBox(
              width: 400,
              child: DemoPage(),
            ),
          ),
        ),
      ),
    );
  }
}

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

  [@override](/user/override)
  State<DemoPage> createState() => _DemoPageState();
}

/// 大多数这些状态字段只是为了调试发生了什么
class _DemoPageState extends State<DemoPage> {
  bool? isPlatformSupported;
  String? usernameNote;
  String? passwordNote;
  bool obscurePassword = true;
  final usernameController = TextEditingController();
  final passwordController = TextEditingController();
  String loginToken = '加载中...';

  [@override](/user/override)
  void initState() {
    super.initState();
    unawaited(initPlatformState());
    usernameController.addListener(resetUsernameNote);
    passwordController.addListener(resetPasswordNote);
    AutologinPlugin.setup(
      domain: 'rekire.github.io',
      appId: 'eu.rekisoft.flutter.autologin',
      appName: 'Autologin Demo',
    );
    AutologinPlugin.requestLoginToken().then((value) async {
      if (value != null) {
        setState(() => loginToken = value);
      } else {
        final hasZeroTouchSupport = (await AutologinPlugin.performCompatibilityChecks()).hasZeroTouchSupport;
        setState(() => loginToken = hasZeroTouchSupport ? '这是首次启动' : '平台不支持');
        if (hasZeroTouchSupport) {
          await AutologinPlugin.saveLoginToken('首次启动 ${DateTime.now()}');
        }
      }
    }).onError((error, stackTrace) {
      setState(() => loginToken = error.toString());
    });
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    usernameController.removeListener(resetUsernameNote);
    passwordController.removeListener(resetPasswordNote);
  }

  // 平台消息是异步的,所以我们初始化在一个异步方法中。
  Future<void> initPlatformState() async {
    final isSupported = await AutologinPlugin.isPlatformSupported;
    setState(() => isPlatformSupported = isSupported);
  }

  void resetUsernameNote() {
    setState(() => usernameNote = null);
  }

  void resetPasswordNote() {
    setState(() => passwordNote = null);
  }

  Future<void> requestCredentials() async {
    final credentials = await AutologinPlugin.requestCredentials();

    if (mounted) {
      setState(() {
        if (credentials?.username != null) {
          usernameController.text = credentials!.username!;
          usernameNote = null;
        } else {
          usernameController.text = '';
          usernameNote = 'API未提供用户名';
        }
        if (credentials?.password != null) {
          passwordController.text = credentials!.password!;
          passwordNote = null;
        } else {
          passwordController.text = '';
          passwordNote = 'API未提供密码';
        }
      });
    }
  }

  Future<void> saveCredentials() async {
    final success = await AutologinPlugin.saveCredentials(
      Credential(username: usernameController.text, password: passwordController.text, domain: 'rekire.github.io'),
    );

    if (!success && mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('保存凭证失败!'),
        ),
      );
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (isPlatformSupported != true)
          const Padding(
            padding: EdgeInsets.only(bottom: 16),
            child: Text('⚠️ 此${kIsWeb ? '浏览器' : '平台'}不支持 ⚠️'),
          ),
        TextFormField(
          controller: usernameController,
          textInputAction: TextInputAction.next,
          autofillHints: const [AutofillHints.username],
          decoration: InputDecoration(
            border: const OutlineInputBorder(),
            labelText: '用户名',
            helperText: usernameNote,
          ),
          onFieldSubmitted: (_) => saveCredentials(),
        ),
        const SizedBox(height: 16),
        TextFormField(
          controller: passwordController,
          obscureText: obscurePassword,
          textInputAction: TextInputAction.send,
          keyboardType: TextInputType.visiblePassword,
          autofillHints: const [AutofillHints.password],
          decoration: InputDecoration(
            border: const OutlineInputBorder(),
            labelText: '密码',
            helperText: passwordNote,
            suffixIcon: IconButton(
              icon: Icon(obscurePassword ? Icons.visibility : Icons.visibility_off),
              onPressed: () {
                setState(() => obscurePassword = !obscurePassword);
              },
              tooltip: obscurePassword ? '显示密码' : '隐藏密码',
            ),
          ),
          onFieldSubmitted: (_) => saveCredentials(),
        ),
        const SizedBox(height: 16),
        FilledButton(
          onPressed: isPlatformSupported == true ? saveCredentials : null,
          child: const Text('保存凭证'),
        ),
        const SizedBox(height: 8),
        OutlinedButton(
          onPressed: () {
            usernameController.text = 'Some-Username';
            passwordController.text = r'Example-P@§$w0rd!';
          },
          child: const Text('输入示例数据'),
        ),
        const SizedBox(height: 8),
        OutlinedButton(
          onPressed: isPlatformSupported == true ? requestCredentials : null,
          child: const Text('请求登录数据'),
        ),
        const SizedBox(height: 8),
        Text('登录令牌: $loginToken'),
      ],
    );
  }
}

更多关于Flutter自动登录插件autologin_darwin的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自动登录插件autologin_darwin的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用autologin_darwin插件实现自动登录功能的示例代码。这个插件主要用于macOS和iOS平台,通过Keychain服务来实现自动登录功能。

首先,确保你的Flutter项目已经创建,并且已经添加了autologin_darwin依赖。在pubspec.yaml文件中添加以下依赖:

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

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

接下来,你需要根据你的平台(macOS或iOS)进行一些配置。这里以macOS为例进行说明。

macOS 配置

  1. macos/Runner/Info.plist中添加权限请求

    <key>NSAppleEventsUsageDescription</key>
    <string>We need to access the keychain for automatic login</string>
    <key>IOKitPersonalities</key>
    <dict>
        <key>HIDDevice</key>
        <dict>
            <key>CFBundleIdentifier</key>
            <string>com.yourcompany.yourapp</string>
            <key>IOKitPersonalities</key>
            <dict>
                <key>HIDKeyboardEventDriver</key>
                <dict>
                    <key>CFBundleIdentifier</key>
                    <string>com.apple.driver.AppleHIDKeyboardEventDriver</string>
                    <key>IOClass</key>
                    <string>IOHIDDevice</string>
                    <key>IOMatchCategory</key>
                    <string>IODefaultMatchCategory</string>
                </dict>
            </dict>
        </dict>
    </dict>
    

    注意:上面的Info.plist配置可能需要根据实际情况进行调整,特别是CFBundleIdentifier部分。

  2. 在Flutter代码中实现自动登录功能

    import 'package:flutter/material.dart';
    import 'package:autologin_darwin/autologin_darwin.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      String _loginStatus = 'Not Logged In';
    
      @override
      void initState() {
        super.initState();
        _checkAutoLogin();
      }
    
      Future<void> _checkAutoLogin() async {
        String? username = await AutologinDarwin.getUsername();
        String? password = await AutologinDarwin.getPassword();
    
        if (username != null && password != null) {
          setState(() {
            _loginStatus = 'Auto Logged In as $username';
            // 在这里处理自动登录后的逻辑,比如导航到主页面
          });
        } else {
          setState(() {
            _loginStatus = 'Not Logged In';
            // 在这里处理未登录的逻辑,比如显示登录页面
          });
        }
      }
    
      Future<void> _saveCredentials(String username, String password) async {
        await AutologinDarwin.saveUsername(username);
        await AutologinDarwin.savePassword(password);
        setState(() {
          _loginStatus = 'Credentials Saved';
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: Text('Auto Login Example'),
            ),
            body: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text('Login Status: $_loginStatus'),
                  SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: () async {
                      // 模拟用户输入用户名和密码
                      String username = 'testuser';
                      String password = 'testpassword';
                      await _saveCredentials(username, password);
                    },
                    child: Text('Save Credentials'),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

在这个示例中,我们首先检查是否已经保存了用户名和密码,如果保存了,就认为用户已经自动登录。然后提供了一个按钮来模拟保存用户名和密码的操作。

注意事项

  1. 权限请求:在实际应用中,你可能需要处理系统权限请求对话框,确保用户授予了访问Keychain的权限。
  2. 安全性:Keychain服务在macOS和iOS上是相对安全的,但仍然建议不要在Keychain中保存敏感信息(如明文密码),考虑使用更安全的存储方式,比如使用Keychain存储加密后的数据。
  3. 平台限制autologin_darwin插件仅支持macOS和iOS,如果你需要跨平台支持,可能需要考虑其他方案。

希望这个示例能帮到你!如果有更多问题,欢迎继续提问。

回到顶部