ARTICLE DETAIL

资讯详情

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

Sa-Token 内置防火墙 SaFirewallStrategy:拦截危险请求的 hook 机制与完整配置指南

Sa-Token 内置防火墙 SaFirewallStrategy:拦截危险请求的 hook 机制与完整配置指南 Sa-Token 内置防火墙 SaFirewallStrategy拦截危险请求的 hook 机制与完整配置指南【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-TokenSa-Token 内置了防火墙组件SaFirewallStrategy用于在请求进入业务逻辑之前拦截双斜杠 path、目录遍历、非法 Host、危险请求头/参数等可能造成攻击的危险请求。本篇基于官方文档 firewall.md 并结合sa-token-core中的源码实现完整讲清防火墙的 9 个默认 hook 校验规则、四种配置方式重载默认配置、注册新 hook、IOC 自动注入、自定义失败处理以及请求被拦截后的实际响应行为读完后你可以直接在自己的项目中启用并定制这套请求级防护能力。一、组件定位为什么需要防火墙级拦截典型的鉴权绕行攻击会提交形如/test//login的请求 path——路径中出现双斜杠意图利用框架的路径解析差异绕过路由匹配或权限规则。Sa-Token 对此类请求的默认处理是强制截断请求直接响应如下纯文本内容非法请求/test//login这个响应文案并非写死而是由 path 校验 hook 抛出的RequestPathInvalidException(非法请求 requestPath)异常信息而来见 RequestPathInvalidException它是FirewallCheckException的子类。防火墙组件的核心实现是单例类 SaFirewallStrategy源码标注since 1.40.0hook 机制自 1.41.0 引入。它内部维护一个checkHooks列表check字段是一个顺序遍历所有 hook 的校验函数// SaFirewallStrategy.java核心结构 public static final SaFirewallStrategy instance new SaFirewallStrategy(); public ListSaFirewallCheckHook checkHooks new ArrayList(); public SaFirewallCheckFunction check (req, res, extArg) - { for (SaFirewallCheckHook checkHook : checkHooks) { checkHook.execute(req, res, extArg); } }; public SaFirewallCheckFailHandleFunction checkFailHandle null;每个 hook 都实现了函数式接口 SaFirewallCheckHookFunctionalInterface public interface SaFirewallCheckHook { void execute(SaRequest req, SaResponse res, Object extArg); }hook 之间通过抛异常来传递校验结果抛FirewallCheckException或其子类表示拦截而白名单 hook 抛出StopMatchException表示终止后续校验、放行请求。这种hook 列表 异常驱动的设计允许开发者自由增删、重排校验规则。二、框架默认的 9 个 hook 拦截规则在SaFirewallStrategy的私有构造函数中SaFirewallStrategy.java L44-L55框架按以下顺序注册了默认 hook顺序hook 类规则说明默认行为源码核实1SaFirewallCheckHookForWhitePath请求 path 白名单放行whitePaths默认为空命中则抛StopMatchException跳过全部后续校验直接放行2SaFirewallCheckHookForBlackPath请求 path 黑名单校验blackPaths默认为空命中则拦截3SaFirewallCheckHookForPathDangerCharacter请求 path 危险字符校验内置危险字符表见下文path 中出现即拦截4SaFirewallCheckHookForPathBannedCharacter请求 path 禁止字符校验始终检查非可打印 ASCII 字符%号检查默认关闭5SaFirewallCheckHookForDirectoryTraversal请求 path 目录遍历符检测检查.、..、//等非法结构见下文6SaFirewallCheckHookForHostHost 检测isCheckHost默认false即默认不校验7SaFirewallCheckHookForHttpMethod请求 Method 检测isCheckMethod默认true默认允许 GET/POST/PUT/DELETE/HEAD/OPTIONS/PATCH/TRACE/CONNECT8SaFirewallCheckHookForHeader请求头检测notAllowHeaderNames默认为空即默认不拦截任何请求头9SaFirewallCheckHookForParameter请求参数检测notAllowParameterNames默认为空即默认不拦截任何参数从源码结构看白名单 hook 被刻意放在第一位一旦请求 path 命中白名单StopMatchException会让整个校验链立即终止后续黑名单、危险字符等规则都不会再执行——这保证了白名单路径的绝对放行语义。危险字符表PathDangerCharacterSaFirewallCheckHookForPathDangerCharacter.java L42-L55 中内置了如下默认危险字符path 中contains任意一项即被拦截public ListString dangerCharacter new ArrayList(Arrays.asList( //, // 双斜杠鉴权绕行 \\, // 反斜杠 %2e, %2E, // 编码后的 . %2f, %2F, // 编码后的 / %5c, %5C, // 编码后的 \ ;, %3b, %3B, // 分号部分框架的路径分隔符 %25, // 编码后的 %二次编码绕过 \0, %00, // 空字符部分容器历史截断漏洞 \n, %0a, %0A, // 换行符日志注入 / CRLF 注入 \r, %0d, %0D, // 回车符 \u2028, \u2029 // Unicode 行/段分隔符JS 解析差异 ));注意这里同时列出了字符本体与其百分号编码形式意味着//写成%2F%2F也无法绕过。目录遍历检测DirectoryTraversalisPathValid 方法 L55-L91 采用静态方法实现校验规则为path 为null或空串 → 非法path 必须以/开头根路径/单独视为合法按/拆分组件后除首位外不允许出现空组件即禁止//无论出现在开头还是中间任何组件等于.或..均非法拦截../目录遍历。源码中附带的测试用例注释直观展示了判定边界/user/info 合法 /user/info/.. 非法末尾 /.. /user/info/../get 非法中间 /../ /user/info/.js 合法点只是后缀不是组件 /.abcdef 合法隐藏文件风格命名 //user 非法多余斜杠 user/../info 非法不以 / 开头禁止字符检测PathBannedCharacter该 hook 的 execute 方法 做两件事通过SaFoxUtil.hasNonPrintableASCII(requestPath)检查 path 是否含非可打印 ASCII 字符如控制字符含则拦截仅当bannedPercentage为true时才额外禁止 path 中出现%字符默认false。如果你的应用对 path 完全不做 URL 解码、或希望彻底杜绝编码类绕过可通过resetConfig(true)开启严格模式。Host / Method / Header / Parameter 四个开关型 hookHostisCheckHost默认false。开启后allowHosts列表支持通配符经SaStrategy.instance.hasElement匹配之外的 Host 会被拦截可用于防御 Host 头注入。MethodisCheckMethod默认true默认放行标准 9 种方法SaHttpMethod枚举全部成员。可收紧为仅允许 GET/POST。HeadernotAllowHeaderNames默认空列表。配置后请求中出现任一指定请求头即拦截例如可禁止X-Forwarded-Host类可被伪造的头。ParameternotAllowParameterNames默认空列表。配置后请求携带任一指定参数名即拦截。三、四种配置方式官方文档完整实操1. 重载默认 hook 配置resetConfig以增加请求 path 黑名单为例使用如下代码Configuration public class SaTokenConfigure { PostConstruct public void saTokenPostConstruct() { SaFirewallCheckHookForBlackPath.instance.resetConfig(/abc); } }现在从浏览器访问/abc将会被防火墙组件直接拦截响应纯文本非法请求/abc需要注意resetConfig的语义是先清空原列表、再写入新值源码中均为clear()后addAll而非追加。除SaFirewallCheckHookForBlackPath以外其它所有 hook 均可通过此方式重载配置各 hook 的resetConfig签名如下hookresetConfig 签名WhitePathresetConfig(String... paths)BlackPathresetConfig(String... paths)PathDangerCharacterresetConfig(String... character)PathBannedCharacterresetConfig(boolean bannedPercentage)HostresetConfig(boolean isCheckHost, String... allowHosts)HttpMethodresetConfig(boolean isCheckMethod, String... methods)HeaderresetConfig(String... notAllowHeaderNames)ParameterresetConfig(String... notAllowParameterNames)2. 注册新的 hook 规则你可以使用如下代码注册新的 hook 规则PostConstruct public void saTokenPostConstruct() { // 注册新 hook 演示拦截所有带有 pwd 参数的请求拒绝响应 SaFirewallStrategy.instance.registerHook((req, res, extArg)-{ if(req.getParam(pwd) ! null) { throw new FirewallCheckException(请求中不可包含 pwd 参数); } }); }registerHook默认将新 hook 追加到列表末尾。如果希望调整优先级源码还提供了两个补充方法见 SaFirewallStrategy.java L71-L83registerHookToFirst(checkHook)注册到第一位。注意 javadoc 特别警告——第一位会优先于白名单判断执行如果你希望白名单 hook 保持最高优先级应改用registerHookToSecondregisterHookToSecond(checkHook)注册到第二位即白名单之后。除了注册新 hook 规则你还可以移除默认 hook来删减你认为不必要存在的校验规则// 移除指定类型的 hook 验证 SaFirewallStrategy.instance.removeHook(SaFirewallCheckHookForHost.class);3. 利用自动注入特性注册 hook如果你的项目属于 IOC 环境例如 SpringBoot 项目还可以把 hook 声明为 Bean 自动注入注册// 自定义防火墙校验 hook Component public class SaFirewallCheckHookForXxx implements SaFirewallCheckHook { Override public void execute(SaRequest req, SaResponse res, Object extArg) { System.out.println(----------- 自定义防火墙校验 hook ); } }这种自动注入在 starter 中确实存在对应实现例如 SaBeanInject.java L250 在启动时收集容器中的SaFirewallCheckHookBean 并调用SaFirewallStrategy.instance.registerHook(hook)完成注册对应的行为验证可参考 SaBeanInjectMethodTest.java L128断言checkHooks中包含自定义 hook。4. 指定异常处理checkFailHandle被防火墙拦截的请求默认不会做出格式化响应因为通常这些请求为非正常业务请求只需阻断即可无需前端依照响应做出页面提示默认行为是输出异常的纯文本 message。如果你的业务切实需要对防火墙拦截做出格式化响应可以通过以下代码完成PostConstruct public void saTokenPostConstruct() { // 指定防火墙校验不通过时的处理方案 SaFirewallStrategy.instance.checkFailHandle (e, req, res, extArg) - { System.out.println(防火墙校验不通过 e.getMessage()); try { HttpServletResponse response (HttpServletResponse)res.getSource(); response.setContentType(application/json;charsetUTF-8); String resJson SaResult.error(e.getMessage()).toString(); response.getWriter().print(resJson); response.getWriter().flush(); } catch (IOException ex) { throw new RuntimeException(ex); } }; }浏览器将得到以下 json 格式响应{ code: 500, msg: 非法请求/abc, data: null }checkFailHandle对应函数式接口 SaFirewallCheckFailHandleFunction签名为run(FirewallCheckException e, SaRequest req, SaResponse res, Object extArg)其中extArg为框架预留的扩展参数starter 过滤器调用时传入的是null。四、拦截链路过滤器层如何消费防火墙结果从源码结构看防火墙的check函数由框架的过滤器层统一驱动。以 SaFirewallCheckFilter 为例其doFilter流程清晰展示了三类异常的归宿try { SaFirewallStrategy.instance.check.execute(saRequest, saResponse, null); } catch (StopMatchException ignored) { // 白名单命中终止校验链放行 } catch (BackResultException e) { SaTokenOperateUtil.writeResult(response, e.getMessage()); return Continue.FALSE; } catch (FirewallCheckException e) { if (SaFirewallStrategy.instance.checkFailHandle null) { SaTokenOperateUtil.writeResult(response, e.getMessage()); // 默认纯文本 } else { SaFirewallStrategy.instance.checkFailHandle.run(e, saRequest, saResponse, null); } return Continue.FALSE; // 阻断请求 }由此可归纳出完整的拦截语义校验通过Continue.TRUE请求继续向下游业务逻辑传递白名单命中StopMatchException被忽略同样放行命中拦截规则写入响应默认纯文本 message 或自定义checkFailHandle并返回Continue.FALSE截断请求链——此时请求不会进入任何 Controller也不会触发业务异常处理器。其它 starter 的集成过滤器如 Spring Boot、Solon 等模块采用同一消费模式防火墙 hook 本身与 Web 容器无关只在请求进来时被统一调用。相关行为可用 SaFirewallCheckFilterTest 与 SaFirewallStrategyTest、SaFirewallHooksTest 中的用例进行验证。五、使用建议结合上述源码事实给出几条落地建议默认配置即有防护无需任何配置双斜杠 path、编码绕过、目录遍历、非可打印字符、非法 Method 已被 9 个默认 hook 覆盖/test//login类请求开箱即被拦截按需收紧 Host/请求头/参数Host、Header、Parameter三个 hook 默认处于空规则状态属于预留开关多域名、反 Host 头注入等场景应显式resetConfig启用白名单语义是全放行SaFirewallCheckHookForWhitePath命中即终止整条校验链配置白名单前请确认该 path 确实无需任何危险字符检查修改危险字符表要整体替换PathDangerCharacter.resetConfig是替换而非追加自定义时建议基于源码中的默认列表扩展避免遗漏二次编码、CRLF 注入等场景hook 顺序即优先级新 hook 默认追加在末尾需要高于白名单的优先级时慎用registerHookToFirst一般应使用registerHookToSecond保持白名单最高优先级源码 javadoc 明确提示了这一点。参考文档firewall.md核心源码SaFirewallStrategy、hooks 目录、FirewallCheckException。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表