Flutter二维码安全验证插件secure_qr_validator的使用

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

Flutter二维码安全验证插件secure_qr_validator的使用

Secure QR Validator

一个强大的Flutter包,用于使用经过实战测试的加密、数字签名和可定制业务规则来验证安全二维码。适用于访问控制、票务和安全文档验证。

兼容性

  • 🔐 与 Secure QR Generator 包兼容

目录

功能

安全验证

  • AES 加密:支持行业标准的AES-256加密
  • 数字签名:HMAC-SHA256签名验证
  • 时间验证:基于时间的有效期验证
  • 重放保护:内置机制防止重放攻击
  • 篡改检测:自动检测被修改的二维码

业务规则

  • 灵活的规则引擎:定义自定义验证逻辑
  • 预构建的验证:常见的验证规则,即开即用
  • 规则组合:使用建造者模式组合规则
  • 类型安全:支持数据验证的泛型类型
  • 可扩展性:轻松添加新的验证规则

Flutter集成

  • 现成的小部件:直接使用的验证状态显示
  • 可定制的UI:完全控制样式和动画
  • 响应式:适应不同的屏幕尺寸
  • 平台支持:适用于iOS、Android和Web
  • 暗模式:自动主题适配

安装

  1. 将依赖项添加到您的 pubspec.yaml 文件中:
dependencies:
  secure_qr_validator: ^1.0.2
  1. 安装包:
flutter pub get
  1. 导入包:
import 'package:secure_qr_validator/secure_qr_validator.dart';

快速开始

基本验证

// 初始化验证器
final validator = SecureQRValidator(
  ValidatorConfig(
    secretKey: 'your-secure-key-min-32-chars-long!!!',
    validityDuration: Duration(minutes: 5),
    enableEncryption: true,
    enableSignature: true,
  ),
);

// 执行验证
try {
  final result = await validator.validateQRPayload(qrCodeContent);
  
  if (result.isValid) {
    // 安全地访问验证后的数据
    final userId = result.getData<String>('userId');
    final accessLevel = result.getData<int>('accessLevel');
    
    // 继续执行业务逻辑
    handleValidAccess(userId, accessLevel);
  } else {
    handleInvalidAccess(result.error);
  }
} catch (e) {
  handleError(e);
}

使用业务规则

final validator = SecureQRValidator(
  ValidatorConfig(
    secretKey: 'your-secure-key-min-32-chars-long!!!',
    validityDuration: Duration(minutes: 5),
  ),
  businessRules: [
    // 必填字段
    CommonValidationRules.required(['userId', 'accessLevel']),
    
    // 访问级别范围检查
    CommonValidationRules.numberInRange('accessLevel', 1, 5),
    
    // 自定义业务逻辑
    (data) {
      final accessLevel = data['accessLevel'] as int;
      final role = data['role'] as String?;
      
      if (role == 'admin' && accessLevel < 3) {
        return 'Admin users must have access level 3 or higher';
      }
      return null;  // 验证通过
    },
  ],
);

高级用法

类型安全的数据访问

final result = await validator.validateQRPayload(qrContent);

// 使用类型检查进行安全数据访问
final UserProfile profile = result.getDataModel<UserProfile>(
  'profile',
  (json) => UserProfile.fromJson(json),
);

// 使用默认值
final age = result.getData<int>('age', defaultValue: 0);
final name = result.getData<String>('name', defaultValue: 'Unknown');

// 检查是否存在所有数据
if (result.hasAllData(['email', 'phone'])) {
  final contacts = Contacts(
    email: result.getData<String>('email'),
    phone: result.getData<String>('phone'),
  );
}

自定义验证规则

class DateRangeRule implements ValidationRule {
  final String startField;
  final String endField;
  
  DateRangeRule(this.startField, this.endField);
  
  [@override](/user/override)
  String? validate(Map<String, dynamic> data) {
    final start = DateTime.parse(data[startField] as String);
    final end = DateTime.parse(data[endField] as String);
    
    if (end.isBefore(start)) {
      return 'End date must be after start date';
    }
    return null;
  }
}

// 使用
validator.addRule(DateRangeRule('startDate', 'endDate'));

界面集成

状态指示器

ValidityIndicatorView(
  result: validationResult,
  builder: (context, status, child) => Container(
    padding: EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: status.color,
      borderRadius: BorderRadius.circular(8),
    ),
    child: Row(
      children: [
        Icon(status.icon),
        SizedBox(width: 8),
        Text(status.message),
      ],
    ),
  ),
)

错误处理

try {
  final result = await validator.validateQRPayload(qrContent);
  handleValidationResult(result);
} on ValidationException catch (e) {
  // 处理验证错误(格式无效、过期等)
  showError('验证错误', e.message);
} on SecurityException catch (e) {
  // 处理安全错误(无效签名、解密失败等)
  showError('安全错误', e.message);
} on BusinessRuleException catch (e) {
  // 处理业务规则违规
  showError('业务规则违规', e.message);
} catch (e) {
  // 处理意外错误
  reportError(e);
}

最佳实践

安全

  • 使用强加密密钥(32个字符以上)
  • 启用加密和签名验证
  • 对敏感操作使用较短的有效期
  • 实施适当的密钥管理与轮换
  • 为验证尝试添加速率限制

性能

  • 使用适当的有效期
  • 实施适当的错误处理
  • 在生产环境中分析内存使用情况

集成

  • 使用依赖注入实例化验证器
  • 为安全事件添加日志记录
  • 使用类型安全的数据访问
  • 编写全面的测试

许可证

该项目根据MIT许可证授权——详情请参阅LICENSE文件。


以下是一个完整的示例Demo:

import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
import 'package:secure_qr_validator/secure_qr_validator.dart';

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

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Secure QR Validator Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Secure QR Validator Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  QRViewController? controller;
  String scanResultEncoded = '';
  String scanResultDecoded = '';

  // 配置状态
  late TextEditingController secretKeyController;
  bool enableEncryption = true;
  bool enableSignature = true;
  int validityDuration = 60;

  // 配置
  late ValidatorConfig validatorConfig;
  late SecureQRValidator validator;

  [@override](/user/override)
  void initState() {
    secretKeyController =
        TextEditingController(text: '2024#@#qrcod#orange@##perform#==');
    updateValidator();
    super.initState();
  }

  void updateValidator() {
    validatorConfig = ValidatorConfig(
      secretKey: secretKeyController.text,
      enableEncryption: enableEncryption,
      enableSignature: enableSignature,
      validityDuration: Duration(seconds: validityDuration),
    );
    validator = SecureQRValidator(validatorConfig);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    var scanArea = MediaQuery.of(context).size.width / 1.4;

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextField(
                    controller: secretKeyController,
                    decoration: const InputDecoration(
                        labelText: 'AES-256密钥(可选)'),
                    onChanged: (_) => updateValidator(),
                  ),
                  SwitchListTile(
                    title: const Text('启用加密(可选)'),
                    value: enableEncryption,
                    onChanged: (value) {
                      setState(() {
                        enableEncryption = value;
                        updateValidator();
                      });
                    },
                  ),
                  SwitchListTile(
                    title: const Text('启用签名(可选)'),
                    value: enableSignature,
                    onChanged: (value) {
                      setState(() {
                        enableSignature = value;
                        updateValidator();
                      });
                    },
                  ),
                  Text('有效期($validityDuration秒)'),
                  Slider(
                    value: validityDuration.toDouble(),
                    min: 10,
                    max: 300,
                    divisions: 29,
                    label: '$validityDuration秒',
                    onChanged: (value) {
                      setState(() {
                        validityDuration = value.toInt();
                        updateValidator();
                      });
                    },
                  ),
                ],
              ),
            ),

            // 扫描器
            SizedBox(
              height: 250,
              child: QRView(
                key: GlobalKey(debugLabel: 'QR'),
                onQRViewCreated: onQRViewCreated,
                formatsAllowed: const [BarcodeFormat.qrcode],
                overlay: QrScannerOverlayShape(
                  borderColor: Colors.deepOrangeAccent,
                  borderRadius: 20,
                  borderLength: 40,
                  borderWidth: 20,
                  overlayColor: Colors.grey,
                  cutOutSize: scanArea,
                ),
                onPermissionSet: (ctrl, p) => onPermissionSet(context, ctrl, p),
              ),
            ),

            // 结果
            Container(
              width: double.maxFinite,
              padding: const EdgeInsets.all(20),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  const Text('读取的二维码值:'),
                  Text(scanResultEncoded),
                  const SizedBox(height: 16),
                  const Text('解码后的二维码值:'),
                  Text(scanResultDecoded),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
    log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
    if (!p) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('无权限')),
      );
    }
  }

  onQRViewCreated(QRViewController controller) {
    this.controller = controller;
    bool scanned = false;
    controller.scannedDataStream.listen((scanData) {
      if (!scanned) {
        scanned = true;
        if (!mounted) return;
        setState(() {
          scanResultEncoded = scanData.code ?? '';
          final result = validator.validateQRPayload(scanResultEncoded);

          if (result.isValid) {
            scanResultDecoded = result.data.toString();
          } else if (result.isExpired) {
            scanResultDecoded = '二维码已过期';
          } else {
            scanResultDecoded = '无效的二维码: ${result.error?.message}';
          }
        });
      }
    });
  }

  [@override](/user/override)
  void dispose() {
    controller?.dispose();
    secretKeyController.dispose();
    super.dispose();
  }
}

更多关于Flutter二维码安全验证插件secure_qr_validator的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter二维码安全验证插件secure_qr_validator的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中集成和使用secure_qr_validator插件来进行二维码安全验证的一个示例代码案例。这个插件可以帮助你验证二维码中的数据是否符合特定的安全标准或模式。

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

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

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

接下来,在你的Flutter项目中,你可以按照以下步骤使用secure_qr_validator插件:

  1. 导入必要的包
import 'package:flutter/material.dart';
import 'package:secure_qr_validator/secure_qr_validator.dart';
  1. 创建一个页面来扫描二维码并验证
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: QRCodeScannerPage(),
    );
  }
}

class QRCodeScannerPage extends StatefulWidget {
  @override
  _QRCodeScannerPageState createState() => _QRCodeScannerPageState();
}

class _QRCodeScannerPageState extends State<QRCodeScannerPage> {
  final SecureQrValidator _qrValidator = SecureQrValidator();
  String _scannedData = '';

  Future<void> _scanQRCode() async {
    try {
      // 这里假设你有一个方法来扫描二维码并返回结果
      // 例如,使用 qr_code_scanner 插件
      String result = await Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => QRCodeScanner()),
      );

      setState(() {
        _scannedData = result;
      });

      // 验证扫描到的二维码数据
      bool isValid = await _qrValidator.validate(result);
      if (isValid) {
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('验证成功'),
            content: Text('二维码数据是安全的。'),
            actions: <Widget>[
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: Text('确定'),
              ),
            ],
          ),
        );
      } else {
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('验证失败'),
            content: Text('二维码数据不安全或不符合要求。'),
            actions: <Widget>[
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: Text('确定'),
              ),
            ],
          ),
        );
      }
    } catch (e) {
      print('扫描二维码时出错: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('二维码安全验证'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('扫描到的数据: $_scannedData'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _scanQRCode,
              child: Text('扫描二维码'),
            ),
          ],
        ),
      ),
    );
  }
}

注意:上面的代码示例假设你已经有一个QRCodeScanner页面来扫描二维码并返回结果。你可以使用qr_code_scanner插件来实现这一功能。这里为了简化示例,没有包含QRCodeScanner的具体实现代码。

  1. 实现SecureQrValidator的逻辑(插件内部实现,这里仅作为说明):

虽然我们不能直接修改secure_qr_validator插件的内部逻辑,但通常这个插件会提供一些方法来验证二维码数据。例如,它可能会检查数据格式、签名或加密等。

在真实场景中,_qrValidator.validate(result)方法会包含具体的验证逻辑。如果secure_qr_validator插件支持自定义验证规则,你可能需要通过配置或扩展该插件来实现你的特定需求。

请确保查阅secure_qr_validator插件的官方文档以获取更多关于如何配置和使用该插件的信息。如果插件不支持你的需求,你可能需要考虑自己实现验证逻辑或寻找其他适合的插件。

回到顶部