package cn.lihu.jh.server.ecg; import cn.lihu.jh.framework.security.config.AuthorizeRequestsCustomizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; /** * 前端静态资源与 SPA 页面外壳的免登录规则。 * *

为什么需要这个类

* 把前端打进了 jar 之后,后端开始承担"提供页面与静态资源"的职责, * 但芋道框架默认的静态资源白名单只覆盖脚本与样式: * *
 *   JhWebSecurityConfigurerAdapter#filterChain:
 *   .antMatchers(GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
 * 
* * 因此下列请求会命中兜底规则 {@code anyRequest().authenticated()} 并返回 * {@code 401 账号未登录},表现为**浏览器白屏或掉线**: * * *

页面路径清单的来源

* 取自 {@code ecg_web/src/router/modules/remaining.ts} 的顶层静态路由 * (path 不含动态参数者),以及动态菜单({@code system_menu} 表)可能出现的 * 业务前缀: *
 *   /  /user  /dict  /codegen  /job  /login  /roomselect
 *   /redirect  /sso  /social-login  /403  /404  /500
 *   /calling-screen-big  /calling-screen-room
 *   /ecg/**  /clinic/**  /system/**  /infra/**
 * 
* *

安全性

* 放行的**只是前端页面外壳与静态资源**,不涉及任何数据: * * 注意 {@code /admin-api/**} 特意不在放行之列,避免误伤接口鉴权。 * * @author 心电排队装机系统 */ public class FrontendAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer { /** 静态资源。不含 html/css/js —— 框架已放行,此处补齐其余类型 */ private static final String[] STATIC_RESOURCE_PATTERNS = { "/favicon.ico", "/logo.gif", "/logo.png", "/logo0.png", "/assets/**", "/static/**", }; /** * SPA 页面外壳。 * 必须与 {@link SpaForwardController} 的映射保持同步, * 否则会出现"转发能到、但被安全层拦下 401"的不一致现象。 * *

⚠️ 为什么 ECG 必须逐条精确枚举,不能用 /ecg/**

* ECG 的后端接口与前端页面共用 {@code /ecg/} 命名空间,例如: *
     *   接口: /ecg/screen/big-screen-data、/ecg/queue/page、/ecg/call/next
     *   页面: /ecg/queue、/ecg/room、/ecg/check-type
     * 
* 若此处放行 {@code /ecg/**},则**所有 ECG 接口都会变成免鉴权**, * 属于严重的安全漏洞(未登录即可读写排队数据)。 * 精确路径在 Spring 中不会匹配带子路径的接口,因此是安全的。 */ private static final String[] SPA_PAGE_PATTERNS = { "/", "/index.html", // 来自 remaining.ts 的顶层静态路由 "/user/**", "/dict/**", "/codegen/**", "/job/**", "/login", "/roomselect", "/redirect/**", "/sso", "/social-login", "/403", "/404", "/500", "/calling-screen-big", "/calling-screen-room", // —— ECG 业务页:必须精确,禁止 /ecg/** —— "/ecg/appointment", "/ecg/appointment-confirm", "/ecg/alter", "/ecg/queue", "/ecg/roomselect", "/ecg/doc/routine", "/ecg/doc/dev-ready", "/ecg/doc/dev-install", "/ecg/dev-dismantle", "/ecg/dev-rent", "/ecg/doctor/dev-data-entry", "/ecg/job-statistic", "/ecg/dev-statistic", "/ecg/room", "/ecg/roomstatus", "/ecg/dev-model", "/ecg/device", "/ecg/check-type", // —— 诊室页(接口 /clinic/room/* 带子路径,不冲突)—— "/clinic/room", // —— 系统管理与基础设施页(其接口均位于 /admin-api 下,不冲突)—— "/system/**", "/infra/**", }; @Override public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { registry.antMatchers(STATIC_RESOURCE_PATTERNS).permitAll(); registry.antMatchers(SPA_PAGE_PATTERNS).permitAll(); } }