必须通过结构化方式记录操作人、行为语义、关键参数及数据快照。可采用五种方法:一、全局中间件拦截请求写入数据库;二、db::listen()钩子捕获sql变更;三、注解驱动控制器方法级标记;四、行为钩子监听原子业务事件;五、配置化审计规则表动态匹配。

如果您在ThinkPHP项目中需要对删除用户、修改权限、配置更新等高风险动作进行可追溯的留痕,必须避免仅记录URL和IP,而应通过结构化方式捕获操作人、行为语义、关键参数及数据快照。以下是实现敏感操作安全审计日志的多种方法:
一、基于全局中间件拦截请求并写入数据库
该方法在路由调度完成后、控制器执行前统一捕获请求上下文,确保所有HTTP敏感操作均被覆盖,且能安全提取登录态与参数。中间件位置稳定、逻辑集中,便于统一脱敏与字段校验。
1、在app/middleware.php中注册自定义中间件,例如App\Middleware\SensitiveOperationLogMiddleware。
2、在中间件handle()方法内,先判断当前请求是否匹配敏感路径,如使用in_array($request->rule()->getRuleName(), ['admin/user/delete', 'admin/role/grant'])进行白名单匹配。
3、调用Auth::id()获取已认证用户ID;若失败,则尝试$request->attr('user')['id'](适用于JWT场景);兜底读取X-User-ID Header并校验其签名有效性。
4、调用$request->param()获取参数,并使用array_filter()显式剔除password、token、card_no等敏感键,仅保留如id、status、role_id等审计所需字段。
5、对高危操作(如delete),额外查询操作前数据快照,例如UserModel::find($params['id']),记录用户名、邮箱、状态等不可变标识。
6、将admin_id、action、method、ip、params(JSON格式)、before_data(JSON)、session_id、created_at等字段组装为数组,使用Db::name('audit_log')->insert()写入数据库。
二、利用Db::listen()钩子捕获数据库变更
该方法不依赖控制器或模型,直接监听底层SQL执行,可覆盖Db::table()->delete()、事务内批量更新、存储过程调用等绕过模型的高危操作,是弥补模型事件盲区的关键手段。
1、在app/common.php中调用Db::listen()注册监听器,6.x版本签名需为function($sql, $bind, $time, $type)。
2、根据$type参数识别INSERT/UPDATE/DELETE类型,跳过SELECT及健康检查类查询。
3、从当前请求上下文中提取user_id与ip:使用think\facade\Request::instance()->ip()和Auth::id(),若不可用则设为NULL并标记is_anonymous = 1。
4、解析$bind参数还原主键值,对UPDATE/DELETE操作反查原始数据,调用Model::find()获取before_data并json_encode()存储。
5、提取当前控制器与动作名:$controller = app('http')->getDispatcher()->getCurrent()->getController(); $action = app('http')->getDispatcher()->getCurrent()->getActionName()。
6、将sql、type、user_id、ip、controller、action、before_data、after_data(UPDATE专用)、created_at写入audit_db_log表,字段类型建议使用JSON兼容MySQL 5.7+。
三、通过注解驱动的控制器方法级敏感标记
该方法将敏感语义声明下沉至业务代码层,由中间件通过反射动态识别,实现配置与逻辑分离,避免硬编码路径字符串,提升可维护性与扩展性。
1、在控制器方法上方添加PHPDoc注解,例如/** @Sensitive("删除用户") */ public function delete() { ... }。
2、在中间件中使用反射获取当前执行方法:$reflect = new ReflectionMethod($controller, $action);再调用$reflect->getDocComment()提取注释内容。
3、正则匹配@Sensitive\(["']([^"']+)["']\)提取描述文本,或进一步解析@Sensitive(level="high", target="user")等结构化属性。
4、将提取到的level、target、desc存入日志的sensitive_level、target_type、sensitive_desc字段,供后续审计筛选。
5、结合$request->param()中对应target字段(如target_type为user时检查id键是否存在),增强操作真实性验证。
6、若注解未命中且操作为DELETE/PUT,仍按默认高危策略记录,确保防御无盲区。
四、使用行为钩子监听特定业务事件
该方法适用于登录成功、密码修改、权限授予等明确生命周期的原子事件,通过自定义钩子解耦日志逻辑,避免侵入核心业务流程,响应轻量且触发精准。
1、在登录成功分支末尾调用Hook::listen('user_password_change', ['uid' => $user['id'], 'ip' => request()->ip(), 'old_hash' => $oldPasswordHash])。
2、创建app\common\behavior\PasswordChangeLogBehavior类,在run()方法中校验参数完整性,丢弃缺失uid或ip的钩子调用。
3、对old_hash等敏感字段执行sha256($old_hash)后截取前8位存储,满足审计溯源需求的同时规避明文泄露风险。
4、从钩子参数中提取target_id(如被修改密码的用户ID)、action(固定为user_password_change)、status(默认success),拼装为结构化日志数组。
5、使用Db::name('audit_event')->insert()写入,禁用Log::write()等文件写入方式,防止并发丢失或权限失控。
6、在钩子注册文件behavior.php中确保该行为绑定到user_password_change钩子,且未被其他同名钩子覆盖。
五、配置化审计规则表动态匹配
该方法将敏感路径、风险等级、审计字段白名单等元数据外置为数据库配置,支持运行时热更新,避免每次新增敏感接口都需修改代码,适合中大型权限体系。
1、新建audit_rules表,包含rule_path(如admin.config/update)、level(1=普通,2=高危,3=致命)、desc(“系统配置修改”)、params_whitelist(JSON数组["key", "value"])。
2、在中间件中执行SQL查询:Db::name('audit_rules')->where('rule_path', $request->rule()->getRuleName())->find()。
3、若匹配成功,取出level与params_whitelist,对$request->param()执行array_intersect_key()过滤,仅保留白名单字段。
4、根据level决定是否启用数据快照:level ≥ 2时,自动执行before_data查询并限制只查单条记录,防止单次操作拖垮性能。
5、将rule_id、level、desc、matched_rule_path写入日志表,便于后续按风险等级聚合分析。
6、为rule_path字段添加B-tree索引,确保万级规则下匹配响应时间仍低于5ms。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











