Flutter页面保护插件protected_page的使用

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

Flutter页面保护插件protected_page的使用

📋 目录

⚠️ 重要

截至版本0.0.2,child 已在 RouteConfigAccessGuard 中替换为 childBuilder。这确保了受保护的小部件仅在访问时构建,从而提高应用程序性能。

请参阅CHANGELOG以了解更多信息。

🚀 如何配置库

基本配置

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

  // 全局配置角色和权限
  AccessConfig.setKeys(rolesKey: 'user_roles', permissionsKey: 'user_permissions');
  AccessConfig.setUsePermissions(true);

  // 配置全局提供程序(例如使用令牌)
  AccessConfig.globalProvider = TokenAccessProvider(
    tokenProvider: () async => "TOKEN_JWT",
    decodeToken: (token) async => {
      'user_roles': ['admin'],
      'user_permissions': ['write', 'read']
    },
  );

  AccessConfig.setGlobalFallback(
    (context) => Scaffold(
      body: Center(child: Text('Access Denied.')),
    ),
  );
}

角色和权限配置

AccessConfig.registerRoleGroup('admins', ['admin', 'superadmin']);
AccessConfig.registerRoleGroup('editors', ['editor', 'content_manager']);

添加路由和策略

AccessConfig.addRoutes([
  RouteConfig(
    routeName: '/dashboard',
    policy: AccessPolicy(roles: ['admin']),
    childBuilder: (_) => const DashboardPage(),
    fallback: const Scaffold(body: Text('Access Denied')),
  ),
  RouteConfig(
    routeName: '/settings',
    policy: AccessPolicy(permissions: ['write']),
    childBuilder: (_) => SettingsPage(),
  ),
]);

🛡️ 使用AccessGuard保护小部件

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: AccessGuard(
        routeName: '/dashboard',
        childBuilder: (_) => DashboardPage(),
      ),
    );
  }
}

🔒 使用GetX集成的真实世界示例

AppRoutes 将应用的路由配置集成到GetX中。 它使用 toGetXPages 扩展将自定义的 <code>RouteConfig</code> 对象列表转换为 GetPage 路由。

这种结构使路由定义集中且可重用,更易于迁移到其他路由技术,如Navigator 2.0或Flutter的GoRouter。如果迁移,请将 GetPage 替换为目标路由结构,并相应地调整扩展方法。

/// GetX AppRoutes
class AppRoutes {
  static final List<GetPage> routes = AppRouteConfig.routes.toGetXPages();
}

AppRouteConfig 包含所有应用路由的 <code>RouteConfig</code> 对象列表。 每个路由包括:

  • <code>routeName</code>:路由路径。
  • <code>policy</code>:使用 <code>AccessPolicy</code> 定义基于角色的访问。
  • <code>childBuilder</code>:代表页面的小部件构建器。

这种抽象使路由定义与任何特定路由库解耦。 要迁移到其他路由解决方案,请将 <code>RouteConfig</code> 映射到目标库的路由表示形式(例如,Navigator 2.0的RouteInformationParser)。

/// AppRouteConfig
class AppRouteConfig {
  static final List<RouteConfig> routes = [
    RouteConfig(
      routeName: '/',
      policy: AccessPolicy(roles: ['Admin', 'Supervisor', 'Operador']),
      childBuilder: (_) => HomeView(),
    ),
    RouteConfig(
      routeName: '/login',
      policy: AccessPolicy(),
      childBuilder: (_) => LoginView(),
    ),
    // 或者使用角色组
    RouteConfig(
      routeName: '/reports',
      policy: AccessPolicy(roles: ['AdminSupervisor']),
      childBuilder: (_) => ReportView(),
    ),
  ];
}

Protect 初始化Protected Page库的配置,并将其与GetX连接。 它设置全局设置,例如:

  • 角色/权限键用于令牌解码。
  • 全局访问提供程序用于角色验证。
  • 拒绝访问的备用小部件。
  • 未认证用户的路由重定向。

此类确保基于角色的访问控制在整个应用程序中工作。 要迁移到其他访问管理解决方案,请替换 <code>AccessConfig</code> 为自定义或替代解决方案(例如,在GoRouter中实现中间件)。

// Protect
class Protect {
static void inicialiceProtectPage() {
AccessConfig.setKeys(rolesKey: 'role', permissionsKey: 'user_permissions');

    AccessConfig.globalProvider = TokenAccessProvider(
      tokenProvider: () => TokenUtil.getTokenAsync(),
      decodeToken: (token) async => {
        'role': ['Admin', 'Supervisor', 'Operador'],
      },
    );

    AccessConfig.setGlobalFallback(
      (context) => const Scaffold(
        body: Center(child: Text('Access Denied')),
      ),
    );

    //角色组
    AccessConfig.registerRoleGroup('AdminSupervisor', ['Admin', 'Supervisor']);

    AccessConfig.globalShowLoader = true;
    AccessConfig.setRedirectRoute('/login');

    AccessConfig.addRoutes(AppRouteConfig.routes);

  }
}

此扩展将自定义的 <code>RouteConfig</code> 格式转换为GetX的 <code>GetPage</code> 路由。

  • 公共路由(例如 /login, /auth-operator)绕过 <code>AccessGuard</code>
  • 受保护的路由使用 <code>AccessGuard</code> 来强制执行基于角色的访问验证。

如果迁移到其他路由库,请适应此扩展以将 <code>RouteConfig</code> 映射到新库的路由格式。例如,将 <code>GetPage</code> 替换为Navigator 2.0路由或GoRouter路由。

/// RouteConfigToGetX
extension RouteConfigToGetX on List<RouteConfig> {
  List<GetPage> toGetXPages() {
    return map((route) {
      // 不要在公共路由上使用AccessGuard
      if (route.routeName == '/login') {
        return GetPage(
          name: route.routeName,
          page: () => route.childBuilder(Get.context!),
        );
      }
      if (route.routeName == '/auth-operator') {
        return GetPage(
          name: route.routeName,
          page: () => route.childBuilder(Get.context!),
        );
      }

      // 使用AccessGuard保护受保护的路由
      return GetPage(
        name: route.routeName,
        page: () => AccessGuard(
          routeName: route.routeName,
          childBuilder: route.childBuilder,
        ),
      );
    }).toList();
  }
}

🔄 异步验证

AccessConfig.addRoutes([
  RouteConfig(
    routeName: '/profile',
    policy: AccessPolicy(
      roles: ['user'],
      customValidator: () async {
        await Future.delayed(const Duration(seconds: 2));
        return true;
      },
    ),
    childBuilder: (_) => ProfilePage(),
  ),
]);

🆕 新功能

重定向到登录或备用路由

如果用户未经过身份验证,您可以配置一个 <code>redirectRoute</code> 将他们重定向到登录页面或其他备用路由。

配置

AccessConfig.setRedirectRoute('/login');

如果用户未经过身份验证,他们将自动被重定向到 /login

<code>redirectRoute</code> 的行为

如果未设置 <code>redirectRoute</code>,系统将回退到:

  • 如果提供了,则使用路由特定的备用。
  • 如果没有提供路由特定的备用,则使用全局备用。

全局加载器配置

现在可以控制是否在访问验证期间显示全局加载器。使用 <code>AccessConfig.setGlobalLoader</code> 方法启用或禁用此行为。

默认情况下,加载器处于启用状态,并显示一个简单的占位符小部件 <code>(Center(child: CircularProgressIndicator()))</code>。您可以禁用它或提供一个自定义的全局加载器小部件。

启用或禁用加载器

// 启用全局加载器(默认行为)
AccessConfig.setGlobalLoader(true);

// 禁用全局加载器
AccessConfig.setGlobalLoader(false);

自定义全局加载器

您可以提供一个自定义的全局加载器小部件来覆盖默认行为。

// 设置全局加载器
AccessConfig.setGlobalLoaderWidget((context) => Center(child: Text('Loading, please wait...')));

示例

以下是如何使用全局加载器功能的示例:

void main() {
  AccessConfig.setGlobalLoader(true); // 全局启用加载器
  AccessConfig.setGlobalLoaderWidget((context) => Center(child: CircularProgressIndicator()));

  AccessConfig.setRedirectRoute('/login');
  AccessConfig.globalProvider = MockAccessProvider(
    isAuthenticated: false,
    roles: [],
    permissions: [],
  );

  AccessConfig.addRoutes([
    const RouteConfig(
      routeName: '/dashboard',
      policy: AccessPolicy(roles: ['admin']),
      childBuilder:(_) => Scaffold(body: Text('Dashboard Page')),
      fallback: Scaffold(body: Text('Access Denied')),
    ),
  ]);

  runApp(MaterialApp(
    initialRoute: '/dashboard',
    routes: {
      '/dashboard': (context) => AccessGuard(
            routeName: '/dashboard',
            childBuilder: (_) => Scaffold(body: Text('Dashboard Page')),
          ),
      '/login': (context) => const Scaffold(body: Text('Login Page')),
    },
  ));
}

🚧 高级用例

动态全局备用

AccessConfig.setGlobalFallback(
  (context) {
    final userRoles = AccessConfig.globalProvider?.getRoles();
    if (userRoles?.contains('guest') ?? false) {
      return Scaffold(body: Text('Login Required.'));
    }
    return Scaffold(body: Text('Access Denied.'));
  },
);

📦 安装

在你的 <code>pubspec.yaml</code> 中添加以下内容:

dependencies:
  access_guard: ^latest_version

然后运行:

flutter pub get

🤝 贡献

欢迎贡献!请遵循以下步骤:

  1. Fork仓库
  2. 创建新的分支 (git checkout -b feature/new-feature)
  3. 进行更改并提交 (git commit -m 'Add new feature')
  4. 推送更改 (git push origin feature/new-feature)
  5. 打开Pull请求

贡献指南

  • 保持代码干净易读
  • 为新功能添加测试
  • 在文档中记录更改
  • 遵循项目样式约定

📄 许可证

该项目根据MIT许可证发布 - 详情请参阅LICENSE.md文件。

MIT License

Copyright (c) [year] [full name or organization name]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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

1 回复

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


当然,以下是如何在Flutter项目中使用protected_page插件来保护页面的一个基本示例。protected_page插件允许你创建受保护的路由,只有在满足某些条件(如用户已登录)时,才能访问这些路由。

步骤 1: 添加依赖

首先,在你的pubspec.yaml文件中添加protected_page插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  protected_page: ^最新版本号  # 请替换为最新版本号

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

步骤 2: 创建保护逻辑

接下来,你需要创建一个函数来决定页面是否应该被保护。例如,我们可以创建一个简单的用户登录状态管理。

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

class AuthService with ChangeNotifier {
  bool isLoggedIn = false;

  void login() {
    isLoggedIn = true;
    notifyListeners();
  }

  void logout() {
    isLoggedIn = false;
    notifyListeners();
  }
}

步骤 3: 使用ProtectedRoute

在你的应用中,使用ProtectedRoute来包装需要保护的页面。ProtectedRoute会接受一个builder函数,该函数返回一个布尔值,表示用户是否有权限访问该页面。

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:protected_page/protected_page.dart';
import './auth_service.dart';
import './protected_screen.dart'; // 假设这是你受保护的页面
import './login_screen.dart'; // 假设这是你的登录页面

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => AuthService()),
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        initialRoute: '/',
        routes: {
          '/': (context) => HomeScreen(),
          '/login': (context) => LoginScreen(),
          '/protected': (context) => ProtectedRoute(
            builder: (context) {
              final authService = Provider.of<AuthService>(context, listen: false);
              return authService.isLoggedIn;
            },
            child: ProtectedScreen(),
          ),
        },
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final authService = Provider.of<AuthService>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You are ${authService.isLoggedIn ? 'logged in' : 'logged out'}'),
            ElevatedButton(
              onPressed: authService.isLoggedIn ? () => authService.logout() : () => Navigator.pushNamed(context, '/login'),
              child: Text(authService.isLoggedIn ? 'Logout' : 'Login'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, '/protected'),
              child: Text('Go to Protected Screen'),
            ),
          ],
        ),
      ),
    );
  }
}

步骤 4: 创建受保护的页面和登录页面

创建简单的ProtectedScreenLoginScreen页面。

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

class ProtectedScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Protected Screen'),
      ),
      body: Center(
        child: Text('This is a protected screen.'),
      ),
    );
  }
}

// login_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './auth_service.dart';

class LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final authService = Provider.of<AuthService>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Login'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            authService.login();
            Navigator.pop(context);
          },
          child: Text('Login'),
        ),
      ),
    );
  }
}

这个示例展示了如何使用protected_page插件来创建一个受保护的路由。当用户未登录时,尝试访问受保护的页面将不会成功(通常你会希望重定向到登录页面,但这需要在ProtectedRoute中进行额外的实现)。用户登录后,他们将能够访问受保护的页面。

回到顶部