Auth Controller
AuthController 是一个继承自 Controller 的抽象类。它为保护路由提供了身份验证与授权的契约。你只需在应用中实现一次,然后将其附加到任何需要登录才能访问的路由上。
Abstract Methods
| 方法 | 运行时机 | 应返回的内容 |
|---|---|---|
auth() |
每次请求受保护路由时 | 返回 true 表示允许,返回 false 表示拒绝(并自行处理响应) |
authApi() |
请求受保护的 /api/ 端点时 |
返回 true 表示允许 |
checkLogin() |
由 auth() 调用,用于验证 session |
一个记录 ({bool success, String message, T? user}) |
checkPermission() |
auth() 通过之后,检查 route.permissions |
如果用户拥有所需权限则返回 true |
loginPost() |
处理登录表单路由的 POST 请求 | HTML 响应 |
register() |
处理注册表单的提交(如果你的应用支持自助注册) | HTML 响应 |
newUser() |
创建新用户账号(例如从后台管理面板创建) | HTML 响应 |
logout() |
处理登出路由 | 重定向或 HTML |
updateAuth(email, password, user) |
登录成功之后 | 存储 session/cookie |
removeAuth() |
登出时 | 清除 session/cookie |
index() |
继承自 Controller——当该路由自身没有设置 index/controller 操作时使用的默认处理程序 |
该路由应显示的内容 |
如果你的应用不需要注册功能,通常的做法是让 register()/newUser() 直接抛出 UnimplementedError(),就像示例项目所做的那样——参见下方的示例。
Complete Example
这是来自 Finch 示例项目的 auth controller:
import 'package:finch/finch_route.dart';
import 'package:finch/finch_tools.dart';
import '../models/mock_user_model.dart';
import '../route/web_route.dart';
class AppAuthController extends AuthController<MockUserModel> {
MockUserModel? userLogined;
@override
Future<bool> auth() async {
var res = await checkLogin();
if (!res.success) {
// API requests get a JSON 403, web requests get a redirect
if (rq.isApiEndpoint) {
await rq.renderError(
403,
toData: true,
params: {'message': 'Please login.', 'success': false},
);
} else {
await rq.redirect('/example/form');
}
return false;
}
updateAuth(res.user!.email, res.user!.password, res.user!);
return true;
}
@override
Future<bool> authApi() async {
var auth = rq.authorization;
var mockUser = MockUserModel();
if (auth.type == AuthType.basic) {
String email = auth.getBasicUsername();
String password = auth.getBasicPassword();
return email == mockUser.email && password == mockUser.password;
} else if (auth.type == AuthType.bearer) {
return auth.value == '${mockUser.email} ${mockUser.password}';
}
return false;
}
@override
Future<({bool success, String message, MockUserModel? user})>
checkLogin() async {
var mockUser = MockUserModel();
var userSession = rq.getSession('user', def: '');
if (userSession == mockUser.email) {
return (success: true, message: 'Success.', user: mockUser);
}
return (success: false, message: 'Please login.', user: mockUser);
}
@override
Future<bool> checkPermission() async {
if (rq.route == null || userLogined == null) return false;
var permission = userLogined!.permission;
if (rq.route!.permissions.isNotEmpty &&
!rq.route!.permissions.contains(permission)) {
return false;
}
return true;
}
@override
Future<String> loginPost() async {
var formLogin = LoginForm();
await formLogin.check(
onInvalid: (p0) {},
onValid: (p0) {
var mockUser = MockUserModel();
var email = formLogin.get<String>('email', def: '');
var password = formLogin.get<String>('password', def: '');
if (email == mockUser.email && password == mockUser.password) {
updateAuth(email, password, mockUser);
} else {
rq.addParam('errorLogin', 'form.validation.loginError'.tr);
}
},
);
return homeController.renderView('example/form');
}
@override
Future<String> logout() {
removeAuth();
return rq.redirect('/example/form');
}
@override
Future<String> newUser() => throw UnimplementedError();
@override
Future<String> register() => throw UnimplementedError();
@override
void removeAuth() {
rq.session.remove('user');
rq.removeCookie('user');
userLogined = null;
}
@override
void updateAuth(String email, String password, MockUserModel user) {
userLogined = user;
rq.addSession('user', email);
}
}
Attaching to a Route
final authController = AppAuthController();
FinchRoute(
key: 'example.panel',
path: 'panel',
methods: Methods.ALL,
auth: authController,
permissions: ['admin'],
index: homeController.exampleAuth,
),
受保护路由的请求流程:
- 路由匹配 →
- 调用
auth()→ 若返回false,请求停止 - 调用
checkPermission()→ 若返回false,请求停止 - 执行控制器 / index 处理程序
Authorization Header
对于 API 路由,可以通过 rq.authorization 读取 Authorization 请求头:
var auth = rq.authorization;
if (auth.type == AuthType.bearer) {
String token = auth.value;
// validate token
}
if (auth.type == AuthType.basic) {
String user = auth.getBasicUsername();
String pass = auth.getBasicPassword();
// validate credentials
}
AuthType 还为了向前兼容而声明了 digest、hawk、aws 和 akamai 这几个值,但目前实际上只解析 basic 和 bearer——任何其他方案(或缺失/格式错误的请求头)都会解析为 AuthType.none。
Permissions
Permissions 是一个小型类,包含预定义的权限字符串常量,供你使用,而无需在各处硬编码字符串:
class Permissions {
static final String none = 'none';
static final String superAdmin = 'super-admin';
}
FinchRoute(
key: 'admin.panel',
path: 'admin/panel',
auth: authController,
permissions: [Permissions.superAdmin],
index: adminController.panel,
),
它只定义了这两个常量——对于 none/super-admin 之外的其他权限,请自行定义权限字符串(或扩展 Permissions),并在 checkPermission() 内部以同样的方式检查它们,就像上面示例项目中的 AppAuthController.checkPermission() 所做的那样。