在Angular中,您可以使用canActivate
进行路由防护。
在Flutter中,该如何处理?守卫在哪里?您如何守护路线?
我正在考虑以下方面:
答案 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
...
首先创建一个扩展MaterialPageRoute
或MaterialWithModalsPageRoute
的新类,如果您像我一样,并且想打开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);