yxh
5 天以前 68ff33beff11310e830b0615b5a81f83fad0f683
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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", "/**&#47;*.html", "/**&#47;*.css", "/**&#47;*.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();
    }
 
}