Flutter 中的基于角色的访问控制
当我在推特上发起#30DaysOfFlutter挑战时(不好意思用了这个广告 xD),我从没想过自己能坚持超过4天。但今天是第8天,我觉得进展非常顺利。
虽然我经常在 Twitter 上分享我的 Flutter 学习心得,但今天的主题实在太精彩了,值得单独写一篇博文。我正在用 Flutter 开发一个包含不同用户角色(管理员、经理和普通用户)的副项目。这些角色需要不同的用户界面、功能和数据访问权限!所以我决定不在每个组件里都堆砌 if-else 语句,而是构建一个强大的系统来无缝控制访问权限。而这正是我们今天要学习的内容——如何在 Flutter 中实现基于角色的访问控制 (RBAC)。
起点
我们先从简单的开始!为了探索角色管理,我们将构建一个只有两个文件的基本应用程序app.dart。main.dart把它想象成一张空白画布。暂时忽略花哨的用户界面,我们会逐步增加复杂性。这样,我们就可以一步一步地专注于访问控制的核心概念。
app.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RBAC Demo',
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User model app'),
),
body: Container(),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:userrole/app.dart';
void main() {
runApp(const MyApp());
}
现在我们来介绍一些功能。首先,我们介绍一个简单的按钮,它能创建一个带有消息的提示栏:
import 'package:flutter/material.dart';
class MessageButton extends StatefulWidget {
const MessageButton({Key? key}) : super(key: key);
@override
State<MessageButton> createState() => _MessageButtonState();
}
class _MessageButtonState extends State<MessageButton> {
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: _showMessage,
child: const Text('Show me'),
);
}
_showMessage() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('I am role based'),
),
);
}
}
处理角色的最佳实践不是通过用户界面进行管理,而是应该在服务器端检查每个功能调用中的角色和权限。用户界面角色管理更有利于用户体验。
为了模拟真实世界的身份验证,假设我们在用户成功登录后获取用户数据(包括角色)。虽然我们可以使用安全的 JWT 令牌来实现这一点,但为了简化操作,我们定义用于将此数据存储在本地存储中的纯 JSON 格式。
{
"username": "Sparsh",
"email": "sparsh@gmail.com",
"role": {
"name": "admin",
"level": 3
}
}
我们需要在 中创建一个合适的用户数据容器model.dart。
import 'package:flutter/foundation.dart';
@immutable
class UserData {
final String username;
final String email;
final UserRole role;
const UserData({
required this.username,
required this.email,
required this.role,
});
factory UserData.fromJson(Map<String, dynamic> json) {
return UserData(
username: json['username']!,
email: json['email'],
role: UserRole.fromJson(json['role']),
);
}
}
@immutable
class UserRole {
final String name;
final int level;
const UserRole({
required this.name,
required this.level,
});
factory UserRole.fromJson(Map<String, dynamic> json) {
return UserRole(
name: json['name'],
level: json['level'] as int,
);
}
}
为了让用户能够轻松访问数据,我们将创建一个专门的文件来存放数据,用户可以从应用程序中的任何位置轻松访问该文件。
core.dart
import 'dart:convert' show jsonDecode;
import 'package:userrole/mdoel.dart';
import 'package:flutter/services.dart' show rootBundle;
class Core {
static UserData? _user;
UserData? get user => _user;
Future<void> setUserData() async {
// loads user data from shared preferences and sets it to
// [user]
}
}
main.dart将改为以下内容:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Core core = Core();
await core.setUserData();
runApp(const MyApp());
}
为了避免在每个组件中进行硬编码的角色检查,我们将定义一个有状态的WidgetWithRole类role_handlers.dart。该类与核心类交互以获取数据并集中进行角色验证,从而简化我们的代码。
import 'package:flutter/material.dart';
import 'package:userrole/core.dart';
class WidgetWithRole extends StatefulWidget {
const WidgetWithRole({Key? key, required this.child}) : super(key: key);
final Widget child;
@override
State<WidgetWithRole> createState() => _WidgetWithRoleState();
}
class _WidgetWithRoleState extends State<WidgetWithRole> {
late Core core;
@override
void initState() {
core = Core();
super.initState();
}
bool get isAdmin => core.user?.role.name == "admin";
@override
Widget build(BuildContext context) {
if (isAdmin) {
return widget.child;
}
return Container();
}
}
现在,app.dart可以通过将 MessageButton 包装起来来利用此功能WidgetWithRole,从而无缝实现访问控制。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User model app'),
),
body: const Center(
child: WidgetWithRole(
child: MessageButton(),
),
),
);
}
我们有一个非常棒的角色检查封装器,但是硬编码的“admin”角色感觉有些局限。让我们通过添加allowedRole参数来让它变得更好!你可以将任何角色作为字符串传入,但是枚举类型岂不是更简洁?我们会专门创建一个enums.dart文件来存放这些角色,将它们转换UserRole为枚举类型,并WidgetWithRole相应地更新调用。
model.dart
import 'package:flutter/foundation.dart';
import 'package:userrole/enums.dart';
@immutable
class UserData {
final String username;
final String email;
final UserRole role;
const UserData({
required this.username,
required this.email,
required this.role,
});
factory UserData.fromJson(Map<String, dynamic> json) {
return UserData(
username: json['username']!,
email: json['email'],
role: UserRole.admin,
);
}
}
enums.dart
enum UserRole {
admin('admin', 3);
const UserRole(this.name, this.level);
final String name;
final int level;
@override
String toString() => name;
}
现在,在MyHomePage小部件中,我们将传递allowedRole参数给WidgetWIthRole。
app.dart
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('User model app'),
),
body: const Center(
child: WidgetWithRole(
allowedRole: UserRole.admin,
child: MessageButton(),
),
),
);
}
现在我们将修改检入role_handlers.dart文件:
role_handlers.dart
class WidgetWithRole extends StatefulWidget {
const WidgetWithRole({
Key? key,
required this.child,
required this.allowedRole,
}) : super(key: key);
final Widget child;
final UserRole allowedRole;
@override
State<WidgetWithRole> createState() => _WidgetWithRoleState();
}
class _WidgetWithRoleState extends State<WidgetWithRole> {
late Core core;
@override
void initState() {
core = Core();
super.initState();
}
bool get isAllowed => core.user?.role == widget.allowedRole;
@override
Widget build(BuildContext context) {
if (isAllowed) {
return widget.child;
}
return Container();
}
}
现在,我们已经重构并完善了角色处理程序的版本,我认为是时候引入多角色及其相关的一切了。
首先,让我们更新model.dart并enum.dart支持多个角色及其反序列化,
enum.dart
enum UserRole {
admin('admin', 3),
manager('manager', 2),
user('user', 1);
const UserRole(this.name, this.level);
final String name;
final int level;
@override
String toString() => name;
factory UserRole.fromJson(String? role) {
switch (role) {
case "admin":
return UserRole.admin;
case "manager":
return UserRole.manager;
default:
return UserRole.user;
}
}
}
role_handlers.dart
final List<UserRole> allowedRoles;
// ...
bool get isAllowed => widget.allowedRoles.contains(core.user?.role);
现在app.dart,我们将修改代码,使其能够访问多个角色:
child: WidgetWithRole(
allowedRoles: [
UserRole.admin,
UserRole.manager,
UserRole.user,
],
child: MessageButton(),
),
结论
这样你就可以在 Flutter 应用中引入基于角色的访问控制 (RBAC) 了。还有其他一些方法,例如添加角色层级结构,并仅指定最低级别的角色,这样所有高于该级别的角色都可以访问组件。你可以根据自身需求和应用复杂度选择最合适的方案。
文章来源:https://dev.to/sparshmalhotra/role-based-access-control-in-flutter-4m6c