Flutter中的守卫

时间:2018-06-25 16:16:03

标签: dart flutter

在Angular中,您可以使用canActivate进行路由防护。

在Flutter中,该如何处理?守卫在哪里?您如何守护路线?

我正在考虑以下方面:

  • 用户登录。其令牌存储在“共享首选项”中(正确的令牌存储方式?)
  • 用户关闭了应用程序。
  • 用户再次打开应用。在应用程序启动时,它将确定用户是否已登录(也许是一种检查令牌存储的服务),然后
  • 如果已登录,请加载首页路线
  • 如果未登录,请加载登录页面

5 个答案:

答案 0 :(得分:2)

我认为本身没有路由保护机制,但是您可以在加载应用程序之前在main函数中进行逻辑处理,或者使用{{1}的onGenerateRoute属性}。在您的情况下,执行此操作的一种方法是等待异步函数,该函数在加载初始路由之前检查用户是否已登录。像

MaterialApp

但是您可能还对Shrine应用程序的执行方式感兴趣。在任何情况下,他们都将登录页面作为初始路径,如果用户已登录,则将其删除。这样,用户将看到登录页面,直到确定是否登录为止。我已将{{3 }}。

main() {
  fetchUser().then((user) {
    if (user != null) runApp(MyApp(page: 'home'));
    else runApp(MyApp(page: 'login'));
  });
}

如果您不希望他们登录后完全看到登录页面,请使用第一种方法,您可以通过浏览{{3来控制class ShrineApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Shrine', home: HomePage(), initialRoute: '/login', onGenerateRoute: _getRoute, ); } Route<dynamic> _getRoute(RouteSettings settings) { if (settings.name != '/login') { return null; } return MaterialPageRoute<void>( settings: settings, builder: (BuildContext context) => LoginPage(), fullscreenDialog: true, ); } } 拥有UI之前显示的初始屏幕}}。

答案 1 :(得分:2)

我也为这个问题感到困惑,最终为此使用了FutureBuilder。看看我的路线:

final routes = {
  '/': (BuildContext context) => FutureBuilder<AuthState>(
    // This is my async call to sharedPrefs
    future: AuthProvider.of(context).authState$.skipWhile((_) => _ == null).first,
    builder: (BuildContext context, AsyncSnapshot<AuthState> snapshot) {
      switch(snapshot.connectionState) {
        case ConnectionState.done:
          // When the future is done I show either the LoginScreen 
          // or the requested Screen depending on AuthState
          return snapshot.data == AuthState.SIGNED_IN ? JobsScreen() : LoginScreen()
        default:
          // I return an empty Container as long as the Future is not resolved
          return Container();
      }
    },
  ),
};

如果您想跨多个路径重用代码,则可以扩展FutureBuilder。

答案 2 :(得分:2)

您可以使用auto_route扩展名。该扩展名非常适合处理路由,并提供了使用guards的好方法。请参考文档: https://pub.dev/packages/auto_route#route-guards

答案 3 :(得分:1)

我为Web项目提出了以下解决方案,该解决方案使我可以轻松地引入受保护的路由,而不必担心未经授权的用户能够访问敏感信息。

GuardedRoute类如下:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:kandabis_core/core.dart' as core;

Widget _defaultTransitionsBuilder(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child) {

    return child;
}

class GuardedRoute extends PageRouteBuilder {

    GuardedRoute({
        @required final String guardedRoute,
        @required final String fallbackRoute,
        @required final Stream<bool> guard,
        @required final core.Router router,
        final RouteTransitionsBuilder transitionsBuilder = _defaultTransitionsBuilder,
        final bool maintainState = true,
        final Widget placeholderPage,
    })
    : super(
        transitionsBuilder: transitionsBuilder,
        maintainState: maintainState,
        pageBuilder: (context, animation, secondaryAnimation) =>

            StreamBuilder(
                stream: guard,
                builder: (context, snapshot) {

                    if (snapshot.hasData) {

                        // navigate to guarded route
                        if (snapshot.data == true) {

                            return router.routes[guardedRoute](context);
                        }

                        // navigate to fallback route
                        return router.routes[fallbackRoute](context);
                    }

                    // show a placeholder widget while the guard stream has no data yet
                    return placeholderPage ?? Container();
                }
            ),
    );
}

使用受保护的路线很容易。您可以定义受保护的路由和后备路由(例如登录页面)。 Guard是一个Stream,它决定用户是否可以导航到受保护的路线。这是我的Router类,显示了如何使用GuardedRoute类:

class BackendRouter extends core.BackendRouter {

    BackendRouter(
        this._authenticationProvider,
        this._logger
        );

    static const _tag = "BackendRouter";

    core.Lazy<GlobalKey<NavigatorState>> _navigatorKey =

        core.Lazy(() => GlobalKey<NavigatorState>());

    final core.AuthenticationProvider _authenticationProvider;
    final core.Logger _logger;

    @override
    Map<String, WidgetBuilder> get routes => {

        core.BackendRoutes.main: (context) => MainPage(),
        core.BackendRoutes.login: (context) => LoginPage(),
        core.BackendRoutes.import: (context) => ImportPage(),
    };

    @override
    Route onGenerateRoute(RouteSettings settings) {

        if (settings.name == core.BackendRoutes.login) {

            return MaterialPageRoute(
                settings: settings,
                builder: routes[settings.name]
            );
        }

        return _guardedRoute(settings.name);
    }

    @override
    GlobalKey<NavigatorState> get navigatorKey => _navigatorKey();

    @override
    void navigateToLogin() {

        _logger.i(_tag, "navigateToLogin()");

        navigatorKey
            .currentState
            ?.pushNamed(core.BackendRoutes.login);
    }

    @override
    void navigateToImporter() {

        _logger.i(_tag, "navigateToImporter()");

        navigatorKey
            .currentState
            ?.pushReplacement(_guardedRoute(core.BackendRoutes.import));
    }

    GuardedRoute _guardedRoute(
         String route,
         {
             maintainState = true,
             fallbackRoute = core.BackendRoutes.login,
         }) =>

         GuardedRoute(
             guardedRoute: route,
             fallbackRoute: fallbackRoute,
             guard: _authenticationProvider.isLoggedIn(),
             router: this,
             maintainState: maintainState,
             placeholderPage: SplashPage(),
         );
}

您的应用程序类如下:

class BackendApp extends StatelessWidget {

    @override
    Widget build(BuildContext context) {

        // get router via dependency injection
        final core.BackendRouter router = di.get<core.BackendRouter>();

        // create app
        return MaterialApp(
            onGenerateRoute: (settings) => router.onGenerateRoute(settings),
            navigatorKey: router.navigatorKey,
        );
    }
}

答案 4 :(得分:0)

我的解决方案是建立一个路由防护系统,就像那里的其他库一样,但是我们仍然可以在需要的地方使用原始的Navigator,打开一个模态作为命名路由,链式防护,并添加重定向。它确实很基础,但是可以很容易地构建。

看起来很多,但是您只需要维护3个新文件以及新的防护就可以了:

- router/guarded_material_page_route.dart
- router/route_guard.dart
- router/safe_navigator.dart

// Your guards go in here
- guards/auth_guard.dart
...

首先创建一个扩展MaterialPageRouteMaterialWithModalsPageRoute的新类,如果您像我一样,并且想打开Modal Bottom Sheet package。我叫我GuardedMaterialPageRoute

class GuardedMaterialPageRoute extends MaterialWithModalsPageRoute {
  final List<RouteGuard> routeGuards;

  GuardedMaterialPageRoute({
    // ScrollController is only needed if you're using the modals, as i am in this example.
    @required Widget Function(BuildContext, [ScrollController]) builder,
    RouteSettings settings,
    this.routeGuards = const [],
  }) : super(
    builder: builder,
    settings: settings,
  );
}

您的路线守卫将如下所示:

class RouteGuard {
  final Future<bool> Function(BuildContext, Object) guard;

  RouteGuard(this.guard);

  Future<bool> canActivate(BuildContext context, Object arguments) async {
    return guard(context, arguments);
  }
}

您现在可以像这样将GuardedMaterialPageRoute添加到路由器文件中:

class Routes {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case homeRoute:
        // These will still work with our new Navigator!
        return MaterialPageRoute(
          builder: (context) => HomeScreen(),
          settings: RouteSettings(name: homeRoute),
        );

      case locationRoute:
        // Following the same syntax, just with a routeGuards array now.
        return GuardedMaterialPageRoute(
          // Again, scrollController is only if you're opening a modal as a named route.
          builder: (context, [scrollController]) {
            final propertiesBloc = BlocProvider.of<PropertiesBloc>(context);
            final String locationId = settings.arguments;

            return BlocProvider(
              create: (_) => LocationBloc(
                locationId: locationId,
                propertiesBloc: propertiesBloc,
              ),
              child: LocationScreen(),
            );
          },
          settings: RouteSettings(name: locationRoute),
          routeGuards: [
            // Now inject your guards, see below for what they look like.
            AuthGuard(),
          ]
        );
     ...

像上面在路由器中使用的那样,创建您的异步保护类。

class AuthGuard extends RouteGuard {
  AuthGuard() : super((context, arguments) async {
    final auth = Provider.of<AuthService>(context, listen: false);
    const isAnonymous = await auth.isAnonymous();
    return !isAnonymous;
  });
}

现在,您需要一个新类来处理导航。在这里,您检查是否有访问权限,并只需遍历每个警卫:

class SafeNavigator extends InheritedWidget {

  static final navigatorKey = GlobalKey<NavigatorState>();

  @override
  bool updateShouldNotify(SafeNavigator oldWidget) {
    return false;
  }

  static Future<bool> popAndPushNamed(
    String routeName, {
    Object arguments,
    bool asModalBottomSheet = false,
  }) async {
    Navigator.of(navigatorKey.currentContext).pop();
    return pushNamed(routeName, arguments: arguments, asModalBottomSheet: asModalBottomSheet);
  }

  static Future<bool> pushNamed(String routeName, {
    Object arguments,
    bool asModalBottomSheet = false,
  }) async {
    // Fetch the Route Page object
    final settings = RouteSettings(name: routeName, arguments: arguments);
    final route = Routes.generateRoute(settings);

    // Check if we can activate it
    final canActivate = await _canActivateRoute(route);

    if (canActivate) {
      // Only needed if you're using named routes as modals, under the hood the plugin still uses the Navigator and can be popped etc.
      if (asModalBottomSheet) {
        showCupertinoModalBottomSheet(
            context: navigatorKey.currentContext,
            builder: (context, scrollController) =>
                (route as GuardedMaterialPageRoute)
                    .builder(context, scrollController));
      } else {
        Navigator.of(navigatorKey.currentContext).push(route);
      }
    }

    return canActivate;
  }

  static Future<bool> _canActivateRoute(MaterialPageRoute route) async {
    // Check if it is a Guarded route
    if (route is GuardedMaterialPageRoute) {
      // Check all guards on the route
      for (int i = 0; i < route.routeGuards.length; i++) {
        // Run the guard
        final canActivate = await route.routeGuards[i]
            .canActivate(navigatorKey.currentContext, route.settings.arguments);

        if (!canActivate) {
          return false;
        }
      }
    }

    return true;
  }
}

要使其全部正常工作,您需要将SafeNavigator密钥添加到您的Material应用程序中:

MaterialApp(
  navigatorKey: SafeNavigator.navigatorKey,
  ...
)

现在您可以导航到您的路线,并检查是否可以像这样访问它们:

// Opens a named route, either Guarded or not.
SafeNavigator.pushNamed(shortlistRoute);
// Opens a named route as a modal
SafeNavigator.pushNamed(shortlistRoute, asModalBottomSheet: true);
// Pops the current route and opens a named route as a modal
SafeNavigator.popAndPushNamed(shortlistRoute, asModalBottomSheet: true);