Flutter U2F认证插件u2f的使用

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

Flutter U2F认证插件u2f的使用

FIDO通用第二因素

此包支持iOS、Android、Windows、Linux、macOS和Web上的NFC、USB和Webauthn Fido2密钥。

开始使用

注册

const u2f = U2fV2();
return await u2f.register(
  challenge: '一些随机数据',
  appId: 'example.com',
);

验证

const u2f = U2fV2();
return await u2f.authenticate(
  challenge: '一些随机数据',
  appId: 'example.com',
  keyHandles: [
    // ... 一个已注册的key handles列表
  ],
);

设置

请遵循flutter_nfc_kit包的设置部分。

在iOS上,你的新Info.plist行应该看起来像这样:

<key>NFCReaderUsageDescription</key>
<string>使用NFC与安全设备进行身份验证</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
  <string>A000000308</string>
  <string>A0000005272101</string>
  <string>A000000527471117</string>
  <string>A0000006472F0001</string>
</array>

完整示例代码

以下是一个完整的示例代码,展示了如何使用flutter_u2f插件进行注册和验证。

// 忽略对于文件:避免打印
// 忽略对于文件:实现导入

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:u2f/hid.dart';
import 'package:u2f/u2f.dart';

void main() {
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((record) {
    print('${record.level.name}: ${record.time}: ${record.message}');
  });

  const u2f = U2fV2();
  runApp(const App(u2f: u2f));
}

class App extends StatelessWidget {
  const App({super.key, required this.u2f});

  final U2fV2 u2f;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FIDO2插件示例应用'),
        ),
        body: Row(
          children: [
            Flexible(child: HidDemo(u2f: u2f)),
            const VerticalDivider(),
            Flexible(
              flex: 2,
              child: U2fDemo(
                u2f: u2f,
                challenge: 'F_YaN22CtYQPkmFiEF9a3Q',
                appId: 'localhost',
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class U2fDemo extends StatefulWidget {
  const U2fDemo({
    super.key,
    required this.u2f,
    required this.challenge,
    required this.appId,
  });

  final U2fV2 u2f;

  final String challenge;

  final String appId;

  [@override](/user/override)
  State<U2fDemo> createState() => _U2fDemoState();
}

class _U2fDemoState extends State<U2fDemo> {
  U2fRegistration? registration;
  int? counter;
  String? error;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text('注册信息: ${registration?.keyHandle}'),
          OutlinedButton(
            onPressed: _enroll,
            child: const Text('注册'),
          ),
          if (counter != null) Text('计数器: $counter'),
          if (error != null)
            Text(
              '错误: $error',
              style: const TextStyle(color: Colors.red),
            ),
          OutlinedButton(
            onPressed: registration != null ? _verify : null,
            child: const Text('验证'),
          ),
        ],
      ),
    );
  }

  Future<String> _getMessage() async {
    final methods = await widget.u2f.checkAvailability();

    final text = StringBuffer('请 ');

    for (final m in methods) {
      switch (m) {
        case U2fV2Methods.nfc:
          text.writeln('扫描您的安全密钥');
          break;
        case U2fV2Methods.hid:
          text.writeln('按下您的安全密钥上的按钮');
          break;
        case U2fV2Methods.webauthn:
          text.writeln('按下您的安全密钥上的按钮');
          break;
      }
    }

    return text.toString();
  }

  Future<void> _enroll() async {
    setState(() {
      error = null;
    });

    try {
      final result = await progress<U2fRegistration?>(
        text: Text(await _getMessage()),
        result: () async {
          final registration = await widget.u2f.register(
            challenge: widget.challenge,
            appId: widget.appId,
          );
          return registration;
        }(),
      );

      if (result == null) {
        print('错误');
        return;
      }

      print('注册数据: ${base64.encode(result.registrationData)}');
      print('客户端数据: ${base64.encode(result.clientData)}');
      try {
        print(
          '验证: ${result.verifySignature(result.certificatePublicKey)}',
        );
      } catch (e) {
        print('验证: $e');
      }

      setState(() {
        registration = result;
      });
    } catch (e) {
      setState(() {
        error = e.toString();
      });
    }
  }

  Future<void> _verify() async {
    setState(() {
      error = null;
    });

    try {
      final result = await progress<U2fSignature?>(
        text: Text(await _getMessage()),
        result: () async {
          final signature = await widget.u2f.authenticate(
            challenge: widget.challenge,
            appId: widget.appId,
            keyHandles: [registration!.keyHandle],
          );
          return signature;
        }(),
      );

      if (result == null) {
        print('错误');
        return;
      }

      print('客户端数据: ${base64.encode(result.clientData)}');
      print('签名数据: ${base64.encode(result.signatureData)}');
      print('验证: ${result.verifySignature(registration!.userPublicKey)}');

      setState(() {
        counter = result.counter;
      });
    } catch (e) {
      setState(() {
        error = e.toString();
      });
    }
  }

  Future<T?> progress<T>({
    required Future<T?> result,
    Widget? text,
  }) async {
    final innerContext = Completer<BuildContext>();

    showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        innerContext.complete(context);
        return AlertDialog(
          title: const Text('U2F'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (text != null) text,
              const CircularProgressIndicator(),
            ],
          ),
        );
      },
    );

    try {
      return await result;
    } finally {
      final dialogContext = await innerContext.future;
      if (dialogContext.mounted) {
        Navigator.pop(dialogContext);
      }
    }
  }
}

class HidDemo extends StatefulWidget {
  const HidDemo({super.key, required this.u2f});

  final U2fV2 u2f;

  [@override](/user/override)
  State<HidDemo> createState() => _HidDemoState();
}

class _HidDemoState extends State<HidDemo> {
  List<HidDevice> _hidDevices = const [];
  late Timer _timer;
  Set<U2fV2Methods> _methods = const {};

  [@override](/user/override)
  void initState() {
    super.initState();
    _listDevices();
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      _listDevices();
    });
  }

  [@override](/user/override)
  void dispose() {
    _timer.cancel();
    super.dispose();
  }

  Future<void> _listDevices() async {
    _methods = await widget.u2f.checkAvailability();

    if (_methods.contains(U2fV2Methods.hid)) {
      _hidDevices = (await hid.getDeviceList())
          .where((e) => e.usagePage == 0xf1d0)
          .toList()
        ..sort((a, b) => a.usage?.compareTo(b.usage ?? 0) ?? 0)
        ..sort((a, b) => a.usagePage?.compareTo(b.usagePage ?? 0) ?? 0)
        ..sort((a, b) => a.productId.compareTo(b.productId))
        ..sort((a, b) => a.vendorId.compareTo(b.vendorId))
        ..sort((a, b) => a.productName.compareTo(b.productName));
    }

    if (!mounted) return;
    setState(() {});
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ListView(
      children: [
        if (_methods.contains(U2fV2Methods.nfc))
          const ListTile(
            leading: Icon(Icons.nfc),
            title: Text('近场通信'),
          ),
        if (_methods.contains(U2fV2Methods.webauthn))
          const ListTile(
            leading: Icon(Icons.security),
            title: Text('WebAuthn'),
          ),
        for (final device in _hidDevices)
          ListTile(
            leading: const Icon(Icons.usb),
            title: Text(device.productName),
            subtitle: Text(
              '${device.vendorId.toRadixString(16).padLeft(4, '0')}:${device.productId.toRadixString(16).padLeft(4, '0')}   ${device.serialNumber}',
            ),
          ),
      ],
    );
  }
}

更多关于Flutter U2F认证插件u2f的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter U2F认证插件u2f的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,关于在Flutter中使用U2F(Universal 2nd Factor)认证插件,这通常涉及到与硬件安全模块(HSM)或类似的认证设备进行交互。Flutter本身并没有官方的U2F插件,但你可以通过调用原生平台(Android和iOS)的U2F库来实现这一功能。

下面是一个简化的流程,展示如何在Flutter中通过平台通道调用原生U2F库。需要注意的是,这只是一个概念性的示例,实际实现可能需要根据具体的U2F库进行调整。

1. 设置Flutter项目

首先,创建一个新的Flutter项目:

flutter create u2f_demo
cd u2f_demo

2. 创建平台通道

lib目录下创建一个Dart文件,比如u2f_service.dart,用于定义与原生代码交互的接口:

import 'dart:async';
import 'package:flutter/services.dart';

class U2fService {
  static const MethodChannel _channel = MethodChannel('com.example.u2f_demo/u2f');

  static Future<String?> register(String appId, String challenge) async {
    final String? result = await _channel.invokeMethod('register', {
      'appId': appId,
      'challenge': challenge,
    });
    return result;
  }

  static Future<bool?> authenticate(String appId, String challenge, String registrationId) async {
    final bool? result = await _channel.invokeMethod('authenticate', {
      'appId': appId,
      'challenge': challenge,
      'registrationId': registrationId,
    });
    return result;
  }
}

3. 实现Android端代码

android/app/src/main/java/com/example/u2f_demo/目录下创建一个新的Kotlin文件,比如U2fPlugin.kt,用于处理U2F认证逻辑:

package com.example.u2f_demo

import android.content.Context
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.nio.charset.StandardCharsets
import javax.security.auth.callback.*
import javax.security.auth.login.LoginException
import com.yubico.u2f.U2F
import com.yubico.u2f.U2FClient
import com.yubico.u2f.data.messages.RegisterRequest
import com.yubico.u2f.data.messages.RegisterResponse
import com.yubico.u2f.data.messages.SignRequest
import com.yubico.u2f.data.messages.SignResponse
import android.util.Base64

class U2fPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
    private lateinit var context: Context
    private lateinit var channel: MethodChannel
    private var u2fClient: U2FClient? = null

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPluginBinding) {
        context = flutterPluginBinding.applicationContext
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.example.u2f_demo/u2f")
        channel.setMethodCallHandler(this)
        u2fClient = U2FClient(context)
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        when (call.method) {
            "register" -> {
                val appId = call.argument<String>("appId")!!
                val challenge = call.argument<String>("challenge")!!
                register(appId, challenge, result)
            }
            "authenticate" -> {
                val appId = call.argument<String>("appId")!!
                val challenge = call.argument<String>("challenge")!!
                val registrationId = call.argument<String>("registrationId")!!
                authenticate(appId, challenge, registrationId, result)
            }
            else -> result.notImplemented()
        }
    }

    private fun register(appId: String, challenge: String, result: Result) {
        val registerRequest = RegisterRequest.create(appId, challenge.toByteArray(StandardCharsets.UTF_8))
        u2fClient?.register(registerRequest, object : CallbackHandler {
            override fun handle(callbacks: MutableList<Callback>?) {
                if (callbacks == null || callbacks.isEmpty()) {
                    result.error("U2F_ERROR", "No callbacks provided", null)
                    return
                }
                val callback = callbacks[0] as ChoiceCallback
                // Here you would typically present the user with options to choose from
                // For simplicity, we'll just select the first one
                val selectedChoice = callback.choices[0]
                val registrationResponse = registerRequest.withClientDataHashAndDevice(
                    null, // clientDataHash is typically calculated and passed here
                    selectedChoice as U2FDevice
                )
                val base64RegistrationId = Base64.encodeToString(registrationResponse.registrationData, Base64.NO_WRAP)
                result.success(base64RegistrationId)
            }
        }, object : U2FClient.RegistrationListener {
            override fun onCompleted(registrationResponse: RegisterResponse?) {
                // Handle registration completion (not needed in this simple example)
            }

            override fun onError(e: Exception?) {
                result.error("U2F_ERROR", e?.message ?: "Unknown error", e)
            }
        })
    }

    private fun authenticate(appId: String, challenge: String, registrationId: String, result: Result) {
        // Similar to register method, but using SignRequest and SignResponse
        // This is a simplified outline and would need proper implementation
        result.notImplemented() // Placeholder for actual implementation
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {}

    override fun onDetachedFromActivityForConfigChanges() {}

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

    override fun onDetachedFromActivity() {}
}

注意:上面的Android代码是一个简化的示例,用于展示如何通过平台通道调用U2F库。实际实现需要处理更多的细节,比如处理用户交互、错误处理等。此外,U2FClient的具体实现和API调用可能需要根据实际使用的U2F库进行调整。

4. 实现iOS端代码(可选)

如果你还需要在iOS上实现U2F认证,你需要使用Objective-C或Swift编写相应的原生代码,并通过平台通道与Flutter进行交互。由于iOS上U2F的实现可能涉及更多的安全和权限问题,这里不提供具体的代码示例,但流程与Android类似。

5. 使用插件

在你的Flutter应用中,你可以这样使用U2F服务:

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

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String? registrationId;

  void _register() async {
    String appId = "https://your-app-id.com";
    String challenge = "random_challenge_string";
    String? result = await U2fService.register(appId, challenge);
    setState(() {
      registrationId = result;
    });
  }

  void _authenticate() async {
    if (registrationId == null) return;
    String appId = "https://your-app-id.com";
    String challenge = "random_challenge_string";
    bool? result = await U2fService.authenticate(appId, challenge, registrationId!);
    print("Authentication result: $result");
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('U2F Demo'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Registration ID: $registrationId'),
              ElevatedButton(
                onPressed: _register,
                child: Text('Register'),
              ),
              ElevatedButton(
                onPressed: _authenticate,
                child: Text('Authenticate'),
              ),
            ],
回到顶部