Flutter ー Authentication 认证
原文 https://medium.com/@simbu/flutter-authentication-adb8df7cf673
前言
如果我相信我知道你是谁那我就能让你查看你的个人 应用 application 资料。
身份验证可能是应用程序必须处理的最大的交叉问题。
将它作为一个特性添加到 DigestablePrologue 允许我们多次抽象和重用它,并通过更新一组代码来减少维护。
正文
业务需求
让我们从一个高层次的、故意模糊的业务需求开始:
“该应用程序将能够使用现代身份验证保护对屏幕和 API 的访问”
经过最初的交谈,我们决定接受这个要求:
- 滑动登录屏幕。
- 使用 Microsoft AD 或 Google Firebase 进行身份验证。
- 在设置中注销。
然后,我们举办了一个研讨会,通过示例创建规范:
Login slide up specification
认证服务规范
注销设置规范
并将它们转换为可执行规范:
登录屏幕功能测试
身份验证服务特性测试
注销设置特性测试
为了缩短这篇文章的篇幅,我已经部分实现了注销设置,跳过了即将添加的 AD & Firebase 身份验证服务。
因此,特性测试的结果不再完全反映最初的规范,但是一旦深入细节,范围或方向的改变是正常的。
我发表的所有关于认证和数据访问主题的文章将很快通过一篇摘要文章(一个迷你系列文章)结合在一起。
开始
从 app_config 读取环境。Json 文件,该文件在应用程序启动时加载,并由为 live 和 UAT 进行的 CodeMagic 集成构建注入:
根据环境选择身份验证服务:
AuthenticationServiceStateNotifier selectAuthenticationServiceByEnvironment() {
var environment = GlobalEnvironmentValues.instance.environment;
AuthenticationService authenticationService = environment == Environments.live
? LiveAuthenticationService()
: environment == Environments.uat
? UatAuthenticationService()
: StubbedAuthenticationService();
return AuthenticationServiceStateNotifier(authenticationService);
}
final authenticationServiceProvider = StateNotifierProvider<
AuthenticationServiceStateNotifier, AuthenticationService>(
(ref) => selectAuthenticationServiceByEnvironment(),
);
当应用程序启动时,它会检测存根身份验证服务并自动验证用户:
if (ref.read(authenticationServiceProvider).typeName ==
AuthenticationService.authenticationServiceTypeNameStubbed) {
ref.read(authenticationProvider.notifier).setIsAuthenticated(true);
}
这个设置屏幕是使用一个很棒的包 sets_ui 添加的,它允许用户注销,并调用身份验证服务上的 signOut 方法:
注销设置
我们的自动化特性测试涵盖了所有内容:
登录功能测试报告
认证特性测试报告
在未来的帖子中,当我们添加 UAT 和 Live 环境时,它会在未经身份验证时路由到登录屏幕,并调用真正的身份验证服务来获取访问令牌,这些令牌将被 Flutter Data 用来进行安全的 API 请求。
登录屏幕和身份验证服务已经添加到 DigestablePrologue,设置屏幕添加到 DigestableMe。
引发导航事件 navigation events
为了响应导航,我在路由器上增加了一个观察者
MaterialApp.router(
...
navaigationObservers: [
NavigationEventsObserver(ref.read(eventStoreProvider))
],
...
)
每次导航发生时,它都会在事件总线上引发 Navigated 事件:
/// Raises Nativigated events when the GoRouter navigates.
class NavigationEventsObserver extends NavigatorObserver {
final EventStore eventStore;
NavigationEventsObserver(this.eventStore);
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
raiseNavigatedEvent(route.settings.name ?? "");
}
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
raiseNavigatedEvent(route.settings.name ?? "");
}
void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute }) {
raiseNavigatedEvent(newRoute?.settings.name ?? "");
}
void raiseNavigatedEvent(String routeName) {
even(fn)tStore.bus.fire(Navigated(routeName));
}
}
然后测试可以使用它来证明导航操作已经发生,当前事件存储仅保留最后一个 Navigated 事件。
通过侦听事件并记录它们,我很可能会 extension 这个功能来添加应用程序监视。
Flutter App 生命周期
为了在唤醒时强制执行所需的身份验证,我需要使用重写来捕获应用程序状态(AppLificycleState)。
void didChangeAppLifecycleState(AppLifecycleState state) {
ref.read(appLifecycleStateProvider.notifier)
.setLifecycleState(state);
ref.read(authenticationServiceProvider.notifier)
.checkAuthenticated();
}
可观察的生命周期事件(AppLificycleState) :
- Inactive ー应用程序处于非活动状态,不接收用户输入。这个事件只能在 iOS 上运行,因为在 Android 上没有等效的事件可以映射到
- 暂停ー应用程序当前对用户不可见,不响应用户输入,并在后台运行。这相当于 Android 中的 onPace()
- 应用程序可见并响应用户输入,这相当于 Android 中的 onPostResume()
- 暂停ー 应用 application 暂停。这相当于 Android 中的 onStop; 它不会在 iOS 上触发,因为在 iOS 上没有可映射到的等价事件
功能测试支持你
特性测试让我们知道我们已经破坏了其他特性
这是伟大的,我已经做了一些相当大的变化,但我知道我需要修复,以避免任何回归错误。
测试的契约确保应用程序仍然能够完成早期特性所要求的工作。
特性测试认证
事实证明,要使特性测试通过非常困难,但是为了确保身份验证按计划工作,将它们放在适当的位置是值得的。
主要问题是在测试步骤运行之前从文件加载环境,这意味着我们已经运行了检查身份验证和重定向到登录屏幕的逻辑。
在几次尝试通过代码改变环境失败后,我选择了一套单独的特性测试:
Gherkin 特性测试配置,加载实时配置值文件。
加载不同的环境配置文件:
实时配置值文件
它需要更多的维护,但是很有必要,因为我们将在集成和部署构建中注入配置文件以保护秘密值。
使用事件来解决难以实现的、特征化的测试步骤
登录特性的一些步骤很难实现,因为功能将在使用微应用程序 DigestablePrologue 的父应用程序 DigestableMe 中实现:
When: Making an API request
这意味着我们无法在 DigestablePrologue 中导航到屏幕或发出 API 请求。
这是一个耦合问题,我们可以用事件总线来解决。
应用程序现在只是侦听事件并采取适当的操作,允许我们在步骤中引发事件,而不是实际的导航或 API 调用。
何时: 发出 API 请求事件: API 请求
使用 EventBus 还有其他优点,我们可以在以后添加这些优点,例如记录引发的事件。
规程 ーー 执行特性步骤
使用 Flutter Gherkin 最乏味的部分是创建所有的步骤方法,我将尝试使用构建器来自动创建包含要填写的框架步骤的文件。
与此同时,我使用可视化代码中的高亮来规范所需的步骤,例如:
这样说:
Given: Always on authentication
And: Not authenticated
And: authenticated
When: The application is started
When: The application awakes
When: Making an API request
When: Displaying a restricted screen
When: Displaying an unrestricted screen
Then: The application routes to the 'Login Screen'
And: Records the current screen for redirection
然后将骨架方法添加到步骤文件中
路线授权
具有自定义属性的路由角色,则可以实现基于非角色的路由,大家暂时可以,但可以 extension 。
没有实现登录/id/auth 的受限屏幕,因为它的一些不同之处,它的授权和路由保护,在需要时通过 GoRouter 重定向作为保护。
Packages
结束语
如果本文对你有帮助,请转发让更多的朋友阅读。
也许这个操作只要你 3 秒钟,对我来说是一个激励,感谢。
祝你有一个美好的一天~
© 猫哥
微信 ducafecat
https://wiki.ducafecat.tech
https://ducafecat.com