十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

simple-starter-security

simple-starter-security simple-starter-securitysimple-starter-security是 simple-starter 的安全插件模块提供编译期资源收集、运行时白名单放行、用户认证与权限校验能力以 Axum 中间件形式集成到 Web 服务并重导出全部安全宏。一、基本原理1. 编译期资源收集安全资源接口的权限元数据在编译期通过inventory静态收集#[security]/#[security_controller]#[security_resource]宏展开为ResourceEntrypath_patternresource_idresource_name 模块信息并submit!注册。SecurityPlugin::components_ready阶段遍历 inventory 收集全部条目构建path_pattern - resource_id映射表运行时校验路径唯一性。运行期权限校验时AxumMatchedPath提供实际匹配的路由模式直接查表得到resource_id零反射开销。2. 中间件执行流程安全中间件在业务 handler 之前拦截请求按序执行白名单检查命中白名单直接放行解析用户上下文UserInfoProvider从请求头/令牌解析失败返回 401用户状态检查禁用或过期返回 403权限检查MatchedPath→resource_id→PermissionChecker::check不通过返回 403放行把UserContext附加到 Request extensionshandler 经extract::ExtensionUserContext获取3. 基础路径拼接BasePathProvider默认读web.base_path提供基础路径components_ready阶段将其与资源路径拼接如/api/test/student/add保证MatchedPath与资源映射表 key 精确匹配。4. 协作接口与覆盖语义四个协作接口均通过组件仓库获取trait 对象注入接口默认实现未提供时的行为UserInfoProvider无拒绝所有请求必须由用户提供PermissionChecker有校验resource_ids集合自动使用默认实现SecurityErrorHandler有返回标准 401/403自动使用默认实现BasePathProvider有读web.base_path自动使用默认实现默认实现以on_missing_trait条件注册用户注册自定义实现时自动退位未注册时生效。二、导出的用户可用组件与宏1. SecurityPlugin插件入口usesimple_starter_core::Application;usesimple_starter_security::SecurityPlugin;usesimple_starter_web::WebPlugin;fnmain(){Application::new().register_plugin(WebPlugin::new())// SecurityPlugin 依赖 WebPlugin自动拓扑排序.register_plugin(SecurityPlugin::new().add_whitelist(Some(GET),/health)// 精确匹配.add_whitelist(None,/public/*))// 前缀匹配None 所有方法.run();}方法说明new()创建插件add_whitelist(method, path)添加白名单method为None匹配所有方法path以/*结尾为前缀匹配否则精确匹配collect_resources()静态方法获取编译期收集的全部ResourceEntry任意时刻可调用2. 安全宏#[security_controller]#[security_resource]impl 块形式#[security_controller]必须放在#[rest_controller]外层属性宏执行顺序外 → 内仅显式标记#[security_resource]的方法才注册资源usesimple_starter_security::{security_controller,security_resource};#[security_controller]#[rest_controller(/test)]implTestController{#[post_mapping(/student/add)]#[security_resource]pubasyncfnadd_student(self)-JsonResponse{/* 受保护资源 */}}资源标识默认为Controller名::方法名如TestController::add_student可用#[security_resource(resource_id ..., resource_name ...)]覆盖。#[security]自由函数形式作用于自由函数必须搭配#[get]/#[post]/#[put]/#[delete]使用#[security(resource_id student_query, resource_name 学生查询)]#[get(/student/{id})]#[json_response]asyncfnget_student(axum::extract::Path(id):axum::extract::Pathi64)-JsonResponse{/* ... */}3. UserContext用户上下文由UserInfoProvider构造经中间件附加到请求handler 通过extract::ExtensionUserContext获取pubstructUserContext{pubuser_id:String,// 用户唯一标识pubresource_ids:HashSetString,// 拥有的资源标识集合pubis_disabled:bool,// 是否被禁用pubexpired_at:Optionstd::time::SystemTime,// 过期时间None 永不过期pubextra:Optionserde_json::Value,// 业务自定义扩展字段}implUserContext{pubfnhas_resource(self,resource_id:str)-bool;pubfnis_expired(self)-bool;pubfnis_active(self)-bool;// !is_disabled !is_expired()}4. SecurityError安全错误类型中间件产生的所有错误均经SecurityErrorHandler精确处理变体场景UserDisabled { user_id }用户被禁用UserExpired { user_id }会话已过期MatchedPathUnavailable无法获取路由匹配模式ResourceNotFound { pattern }路径未注册对应资源PermissionDenied { user_id, resource_id }权限校验不通过三、扩展点协作接口自定义实现1. UserInfoProvider必填无默认实现必须注册自定义组件否则所有请求被拒绝#[component]pubstructJwtUserInfoProvider;#[injectable]#[async_trait::async_trait]implUserInfoProviderforJwtUserInfoProvider{asyncfnget_user_context(self,parts:http::request::Parts)-OptionUserContext{letuser_idparts.headers.get(user-id)?.to_str().ok()?.to_string();Some(UserContext{user_id,resource_ids:HashSet::new(),is_disabled:false,expired_at:None,extra:None,})}}2. PermissionChecker可选自定义校验逻辑#[component]pubstructMyPermissionChecker;#[injectable]#[async_trait::async_trait]implPermissionCheckerforMyPermissionChecker{asyncfncheck(self,user_ctx:UserContext,resource_id:str)-bool{// 基于 RBAC / ABAC 的自定义逻辑user_ctx.has_resource(resource_id)}}3. SecurityErrorHandler可选自定义错误响应#[component]pubstructJsonSecurityErrorHandler;#[injectable]#[async_trait::async_trait]implSecurityErrorHandlerforJsonSecurityErrorHandler{asyncfnunauthorized(self,parts:http::request::Parts)-axum::response::Response{letrespJsonResponse{code:401,message:未认证请登录后访问.to_string(),data:Some(serde_json::json!({path:parts.uri.path()})),..Default::default()};(http::StatusCode::UNAUTHORIZED,axum::Json(resp)).into_response()}asyncfnforbidden(self,parts:http::request::Parts,error:SecurityError)-axum::response::Response{letrespJsonResponse{code:403,message:format!(权限不足: {},error),data:Some(serde_json::json!({detail:format!({:?},error)})),..Default::default()};(http::StatusCode::FORBIDDEN,axum::Json(resp)).into_response()}}4. BasePathProvider可选自定义基础路径#[component]pubstructFixedBasePathProvider;#[injectable]implBasePathProviderforFixedBasePathProvider{fnbase_path(self)-String{/v2.to_string()}}四、组合使用示例以下示例串联用户上下文解析、自定义 JSON 错误响应、受保护资源与启动钩子初始化权限缓存usesimple_starter_core::{component,injectable,Application,anyhow};usesimple_starter_security::{SecurityPlugin,UserContext,UserInfoProvider,SecurityErrorHandler,SecurityError};usesimple_starter_web::WebPlugin;usestd::collections::HashSet;// 1. 自定义用户上下文提供者从请求头 user-id 解析#[component]pubstructUserInfoProviderImpl;#[injectable]#[async_trait::async_trait]implUserInfoProviderforUserInfoProviderImpl{asyncfnget_user_context(self,parts:http::request::Parts)-OptionUserContext{letuser_idparts.headers.get(user-id)?.to_str().ok()?.to_string();Some(UserContext{user_id,resource_ids:HashSet::new(),is_disabled:false,expired_at:None,extra:None,})}}fnmain(){Application::new().register_plugin(WebPlugin::new()).register_plugin(SecurityPlugin::new().add_whitelist(Some(GET),/health))// 启动钩子组件就绪后初始化权限数据user_id 1 拥有全部资源权限.add_startup_hook(|_ctx|asyncmove{letresourcesSecurityPlugin::collect_resources();// 把全部 resource_id 授权给 user_id 1 ...Ok(())}).run();}五、配置项[security]节点[security] log_warn true # 是否打印安全警告日志用户禁用、资源未找到、权限不足等
返回列表