设备接口统一由 AuthInterceptor 注入 Device-Sig 头完成认证,删除手动 challenge 签名流程与 transport-key 接口,注册改用本地生成 ts:nonce 做 PoP 验签。
85 lines
3.7 KiB
Java
85 lines
3.7 KiB
Java
package com.secure.demo.config;
|
||
|
||
import org.springframework.beans.factory.annotation.Value;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||
|
||
import java.util.Arrays;
|
||
import java.util.List;
|
||
import java.util.stream.Collectors;
|
||
|
||
/**
|
||
* Web 层配置。
|
||
*
|
||
* <p>前后端分离后,Web 用户端(web-client)与后端跨域通信,需要放开 CORS。
|
||
* 但 <strong>不允许「任意来源 + 携带凭据」</strong>(那是严重风险:任何恶意站点都能借已登录
|
||
* 用户的凭据跨域调用受保护接口)。因此这里改为<strong>域名白名单</strong>:</p>
|
||
* <ul>
|
||
* <li>默认只允许本地开发域名(localhost / 127.0.0.1 的常见端口);</li>
|
||
* <li>生产环境必须通过配置收紧为实际前端域名。</li>
|
||
* </ul>
|
||
*
|
||
* 配置方式(二选一,环境变量优先):
|
||
* <pre>
|
||
* # application.properties
|
||
* app.cors.allowed-origins=http://localhost:3000,https://app.example.com
|
||
* </pre>
|
||
* <pre>
|
||
* # 环境变量(可覆盖上面的配置)
|
||
* export APP_CORS_ALLOWED_ORIGINS="https://app.example.com,https://admin.example.com"
|
||
* </pre>
|
||
*/
|
||
@Configuration
|
||
public class WebConfig implements WebMvcConfigurer {
|
||
|
||
/** 本地开发默认白名单(web-client 常驻端口) */
|
||
private static final String DEFAULT_ALLOWED_ORIGINS =
|
||
"http://localhost:3000,http://127.0.0.1:3000,"
|
||
+ "http://localhost:8080,http://127.0.0.1:8080,"
|
||
+ "http://localhost:5173,http://127.0.0.1:5173";
|
||
|
||
private final List<String> allowedOrigins;
|
||
private final DeviceAuthInterceptor deviceAuthInterceptor;
|
||
|
||
public WebConfig(
|
||
@Value("${app.cors.allowed-origins:}") String configuredOrigins,
|
||
@Value("${APP_CORS_ALLOWED_ORIGINS:}") String envOrigins,
|
||
DeviceAuthInterceptor deviceAuthInterceptor) {
|
||
String raw = (envOrigins != null && !envOrigins.isBlank()) ? envOrigins : configuredOrigins;
|
||
if (raw == null || raw.isBlank()) {
|
||
raw = DEFAULT_ALLOWED_ORIGINS;
|
||
}
|
||
this.allowedOrigins = Arrays.stream(raw.split(","))
|
||
.map(String::trim)
|
||
.filter(s -> !s.isEmpty())
|
||
.collect(Collectors.toList());
|
||
this.deviceAuthInterceptor = deviceAuthInterceptor;
|
||
}
|
||
|
||
@Override
|
||
public void addInterceptors(InterceptorRegistry registry) {
|
||
// 设备签名认证拦截器:仅拦截需要设备认证的路径(/api/device/**、/api/photo/**),
|
||
// 显式排除公开接口(注册、登录、健康检查、CORS 预检)。
|
||
registry.addInterceptor(deviceAuthInterceptor)
|
||
.addPathPatterns("/api/device/**", "/api/photo/**")
|
||
.excludePathPatterns(
|
||
"/api/device/register", // 注册:设备未入库,用请求内公钥做 PoP 验签
|
||
"/api/photo/{photoId}/decrypt", // Web 用户端 Bearer 鉴权,非设备签名
|
||
"/api/photo/{photoId}/decrypt/stream"
|
||
);
|
||
}
|
||
|
||
@Override
|
||
public void addCorsMappings(CorsRegistry registry) {
|
||
registry.addMapping("/api/**")
|
||
// 使用精确域名白名单(非 "*")。allowCredentials(true) 下 "*" 是非法且危险的。
|
||
.allowedOrigins(allowedOrigins.toArray(new String[0]))
|
||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||
.allowedHeaders("*")
|
||
.allowCredentials(true)
|
||
.maxAge(3600);
|
||
}
|
||
}
|