跳到主要内容

安全上下文与安全响应头

本章覆盖两块相关但独立的能力:一是安全功能模块(coco-feature-security)提供的安全上下文桥接,把可信上游写入的主体信息桥接进当前线程;二是 Web 模块(coco-feature-web)提供的安全响应头与 CORS 跨域配置。

安全上下文桥接

功能简介

CocoSecurityContext 保存当前调用方的安全主体(CocoSecurityPrincipal:主体标识、显示名、角色集合、权限集合、附加属性)和认证状态,供鉴权、审计、租户、数据权限等模块读取。它保存在 CocoSecurityContextHolderThreadLocal 中,入口适配器负责在请求进入时设置、结束时清理,业务代码只读取。

import io.github.coco.feature.security.context.CocoSecurityContextHolder;
import io.github.coco.feature.security.context.CocoSecurityContext;

// 读取当前上下文,不存在时返回 Optional.empty()
CocoSecurityContextHolder.current()
.map(CocoSecurityContext::principal)
.ifPresent(principal -> log(principal.id()));

// 要求已有上下文,缺失时抛出未认证异常
CocoSecurityContext context = CocoSecurityContextHolder.requireCurrent();

Web 场景下,桥接由 CocoSecurityWebFilter 完成,它调用 CocoWebSecurityContextResolver 从请求解析出上下文。框架默认注册的解析器是 HeaderCocoWebSecurityContextResolver

从可信 HTTP 头构建上下文

HeaderCocoWebSecurityContextResolver 不做认证,它只消费可信上游(网关、认证过滤器、业务基础设施)已经写入的请求头,把它们组装成已认证的 CocoSecurityContext

请求头默认名称映射到
主体标识X-Coco-Principal-Idprincipal.id(缺失则不构建上下文)
主体显示名X-Coco-Principal-Nameprincipal.name(缺失时回退为主体标识)
角色集合X-Coco-Rolesprincipal.roles(按分隔符拆分)
权限集合X-Coco-Permissionsprincipal.permissions(按分隔符拆分)

角色与权限默认用逗号 , 分隔。该解析器默认关闭coco.security.web.header.enabled=false),避免直接信任外部客户端输入。

必须部署在可信网关之后

这是本能力最关键的边界:一旦开启可信请求头解析,任何能直达应用的请求都可以伪造 X-Coco-Principal-Id 等请求头来冒充任意主体。因此该解析器必须部署在可信网关之后,由网关负责认证并覆盖/剥离这些请求头,绝不能把应用直接暴露到公网。

为提醒这一风险,当 coco.security.web.header.enabled=true 时,CocoSecurityAutoConfiguration 会在启动时打印告警日志,提示该解析器信任上游请求头、必须置于可信网关之后,并建议提供自定义 CocoWebSecurityContextResolver bean 来消除告警。

用自定义解析器替换

如果主体信息来自 JWT、Session 或其它认证机制,直接提供一个自定义 CocoWebSecurityContextResolver bean 即可替换默认实现(默认解析器带 @ConditionalOnMissingBean,同时也会抑制上面的启动告警):

import io.github.coco.feature.security.context.CocoSecurityContext;
import io.github.coco.feature.security.context.CocoSecurityPrincipal;
import io.github.coco.feature.security.web.CocoWebSecurityContextResolver;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Optional;

@Configuration
public class MySecurityConfig {

@Bean
public CocoWebSecurityContextResolver jwtSecurityContextResolver() {
return request -> resolveFromJwt(request);
}

private Optional<CocoSecurityContext> resolveFromJwt(HttpServletRequest request) {
// 校验 JWT 后构建主体
CocoSecurityPrincipal principal = /* ... */ null;
return Optional.ofNullable(principal).map(CocoSecurityContext::authenticated);
}
}

关键配置项

配置项默认值说明
coco.security.web.enabledtrue是否注册 Web 安全上下文桥接过滤器
coco.security.web.header.enabledfalse是否启用可信请求头解析(开启即信任上游头,需置于可信网关后)
coco.security.web.header.principal-id-header-nameX-Coco-Principal-Id主体标识请求头
coco.security.web.header.principal-name-header-nameX-Coco-Principal-Name主体显示名请求头
coco.security.web.header.roles-header-nameX-Coco-Roles角色集合请求头
coco.security.web.header.permissions-header-nameX-Coco-Permissions权限集合请求头
coco.security.web.header.authority-delimiter,角色和权限请求头的分隔符

安全响应头

功能简介

CocoSecurityHeadersFilter 在过滤器链最前端写入一组安全响应头,使后续过滤器、业务代码以及下游产生的错误响应(签名 401、限流 429、未处理异常 500 等)都携带这些头。写在最前端是刻意的:响应一旦提交再写入就太晚,同时后续代码仍可用 setHeader 覆盖框架默认值。任一响应头取值为 null 或空白时不写入。

分两类:

  • 默认开启(提供安全默认值):X-Content-Type-OptionsX-Frame-OptionsReferrer-Policy
  • 默认不写入(需应用显式配置):Content-Security-PolicyPermissions-PolicyStrict-Transport-Security。这三者要么没有普适安全默认值、错误配置会直接破坏应用,要么与具体应用强相关,因此必须由应用自行编写。

如何启用与配置

coco.web.security-headers 控制,默认启用。

coco:
web:
security-headers:
enabled: true
content-type-options: nosniff
frame-options: DENY
referrer-policy: strict-origin-when-cross-origin
content-security-policy: "default-src 'self'"
permissions-policy: "geolocation=(), camera=()"
strict-transport-security: "max-age=31536000; includeSubDomains"

关键配置项

配置项默认值说明
coco.web.security-headers.enabledtrue是否启用安全响应头过滤器
coco.web.security-headers.order最高优先级过滤器执行顺序
coco.web.security-headers.content-type-optionsnosniffX-Content-Type-Options,空白时回退默认
coco.web.security-headers.frame-optionsDENYX-Frame-Options,空白时回退默认
coco.web.security-headers.referrer-policystrict-origin-when-cross-originReferrer-Policy,空白时回退默认
coco.web.security-headers.content-security-policy空(不写入)Content-Security-Policy
coco.web.security-headers.permissions-policy空(不写入)Permissions-Policy
coco.web.security-headers.strict-transport-security空(不写入)Strict-Transport-Security

HSTS 仅在 HTTPS 上写入

Strict-Transport-Security 只在 HttpServletRequest.isSecure()true(安全连接)时才写入;明文 HTTP 上浏览器会忽略它,写入只会掩盖部署配置问题。

需要特别注意:在 TLS 由前置代理终止的部署中,只有应用设置了 server.forward-headers-strategy=framework(或 native)时 isSecure() 才会反映客户端的原始协议,否则 HSTS 会被静默跳过。

CORS 跨域

功能简介

CocoCorsProperties 配置全局 CORS 过滤器的允许来源、方法、请求头、暴露响应头、凭证和预检缓存。CORS 过滤器默认关闭,仅在 coco.web.cors.enabled=true 时才注册(自动配置带 @ConditionalOnProperty(name = "enabled", havingValue = "true"))。

如何启用与配置

coco:
web:
cors:
enabled: true
allowed-origins: ["https://app.example.com"]
allowed-methods: [GET, POST, PUT, DELETE, OPTIONS]
allowed-headers: ["*"]
exposed-headers: [X-Trace-Id]
allow-credentials: false
max-age: 1800

关键配置项

配置项默认值说明
coco.web.cors.enabledfalse是否启用 CORS 跨域过滤器
coco.web.cors.allowed-origins["*"]允许的跨域来源
coco.web.cors.allowed-methodsGET, POST, PUT, DELETE, OPTIONS允许的 HTTP 方法
coco.web.cors.allowed-headers["*"]允许的请求头
coco.web.cors.exposed-headers暴露给客户端的响应头
coco.web.cors.allow-credentialsfalse是否允许发送凭证(Cookie 等)
coco.web.cors.max-age1800预检请求缓存时间(秒)

注意事项

按 CORS 规范,allow-credentials: trueallowed-origins: ["*"] 不能同时使用。开启凭证时请把 allowed-origins 收敛为明确的来源列表。