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 页面外壳的免登录规则。
|
*
|
* <h3>为什么需要这个类</h3>
|
* 把前端打进了 jar 之后,后端开始承担"提供页面与静态资源"的职责,
|
* 但芋道框架默认的静态资源白名单只覆盖脚本与样式:
|
*
|
* <pre>
|
* JhWebSecurityConfigurerAdapter#filterChain:
|
* .antMatchers(GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
|
* </pre>
|
*
|
* 因此下列请求会命中兜底规则 {@code anyRequest().authenticated()} 并返回
|
* {@code 401 账号未登录},表现为**浏览器白屏或掉线**:
|
* <ul>
|
* <li>{@code /logo.gif}、{@code /favicon.ico} —— 登录页与加载动画依赖的图片</li>
|
* <li>{@code /assets/*.svg|png|woff} —— 图标与字体(js/css 恰好被上面规则放行,
|
* 所以最初症状是"接口都通、页面白屏",极具迷惑性)</li>
|
* <li>页面路径 —— 用户刷新或直接访问时浏览器尚未携带 token,
|
* 若要求认证会直接 401,导致"一刷新就掉线"</li>
|
* </ul>
|
*
|
* <h3>页面路径清单的来源</h3>
|
* 取自 {@code ecg_web/src/router/modules/remaining.ts} 的顶层静态路由
|
* (path 不含动态参数者),以及动态菜单({@code system_menu} 表)可能出现的
|
* 业务前缀:
|
* <pre>
|
* / /user /dict /codegen /job /login /roomselect
|
* /redirect /sso /social-login /403 /404 /500
|
* /calling-screen-big /calling-screen-room
|
* /ecg/** /clinic/** /system/** /infra/**
|
* </pre>
|
*
|
* <h3>安全性</h3>
|
* 放行的**只是前端页面外壳与静态资源**,不涉及任何数据:
|
* <ul>
|
* <li>{@code /admin-api/**} 全部业务接口仍走 token 鉴权,未授权拿不到数据</li>
|
* <li>页面外壳本身不含任何患者信息,内容由前端拿到 token 后另行请求</li>
|
* <li>{@code /system/**}、{@code /infra/**} 等放行的是**页面**,
|
* 其数据接口位于 {@code /admin-api/system/**}、{@code /admin-api/infra/**},
|
* 仍在鉴权保护之下</li>
|
* <li>这与合并前 nginx 把静态页面公开托管的行为一致,未降低安全等级</li>
|
* </ul>
|
* 注意 {@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"的不一致现象。
|
*
|
* <h4>⚠️ 为什么 ECG 必须逐条精确枚举,不能用 /ecg/**</h4>
|
* ECG 的后端接口与前端页面共用 {@code /ecg/} 命名空间,例如:
|
* <pre>
|
* 接口: /ecg/screen/big-screen-data、/ecg/queue/page、/ecg/call/next
|
* 页面: /ecg/queue、/ecg/room、/ecg/check-type
|
* </pre>
|
* 若此处放行 {@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<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
|
registry.antMatchers(STATIC_RESOURCE_PATTERNS).permitAll();
|
registry.antMatchers(SPA_PAGE_PATTERNS).permitAll();
|
}
|
|
}
|