背景
API网关,如Spring Cloud Gateway,通常在微服务架构中使用,以在单个端点上公开多个服务。
通过API网关公开IAM解决方案(如Keycloak)并不少见。
某些端点可能需要CORS支持,这通常在API网关级别进行管理。
Keycloak
根据https://www.keycloak.org/about 的描述,“Keycloak是一个面向现代应用程序和服务的开源身份和访问管理解决方案。它使得应用程序和服务的安全变得简单,几乎不需要编写代码。”
Spring Cloud Gateway
根据https://spring.io/projects/spring-cloud-gateway 的描述,Spring Cloud Gateway“提供了一个库,用于在Spring WebFlux之上构建API网关。Spring Cloud Gateway旨在提供一种简单而有效的方式来路由到API,并为它们提供安全、监控/指标和弹性等跨领域关注点。”
CORS
根据https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 的描述,“跨源资源共享(CORS)是一种基于HTTP标头的机制,允许服务器指示浏览器允许加载资源的除自身以外的任何源(域名、方案或端口)。”
如何复现问题
要复现问题,您需要以下设置:
- Keycloak版本12或更高版本(版本12引入了Referrer-Policy响应头,尽管有一个错误报告指出版本11.0.2也存在此问题)
- 启用了CORS的API网关,如Spring Cloud Gateway
以下步骤将复现问题:
使用浏览器导航到Keycloak登录页面(我们使用了Firefox和Chrome),例如本地设置的http://localhost:8080/auth/admin/ 。
在浏览器开发者工具网络标签中观察HTTP响应头“Referrer-Policy: no-referrer”。
提供有效的登录凭据,并点击登录按钮。
观察HTTP响应403禁止和浏览器中的空白页面。
观察HTTP请求头“origin: null”。
如果您无法复现问题,请确保:
- 浏览器与API网关通信,而不是直接与Keycloak通信。
- API网关使用非通配符“Access-Control-Allow-Origin”启用CORS,例如使用以下配置属性:
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOrigins="https://some.domain.org"
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedMethods=GET
解决方案
问题可以通过在API网关中重写Keycloak的HTTP响应头来解决(或者更确切地说,绕过问题),例如,让浏览器接收“Referrer-Policy: same-origin”而不是“Referrer-Policy: no-referrer”。
Spring Cloud Gateway提供了一个方便的RewriteResponseHeaderGatewayFilterFactory来处理这个问题,我们按以下方式设置:
@Bean
public RouteLocator theRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("auth", r ->
r.path("/auth/**")
.filters(f -> f.rewriteResponseHeader("Referrer-Policy", "no-referrer", "same-origin"))
.uri("https://keycloak"))
.build();
}
然后您可以重复以下步骤来观察修复是否有效
使用浏览器导航到Keycloak登录页面,例如本地设置的http://localhost:8080/auth/admin/ 。
在浏览器开发者工具网络标签中观察HTTP响应头“Referrer-Policy: same-origin”。
提供有效的登录凭据,并点击登录按钮。
观察HTTP响应302找到和浏览器中的目标页面。
观察HTTP请求头“origin: http://localhost:8080”。
问题与考虑
为什么API网关拒绝HTTP请求?
当“Referrer-Policy”为“no-referrer”时,浏览器会发送HTTP请求头“origin: null”。
每当HTTP请求中存在“origin”头时,API网关会将其视为CORS请求。CORS请求会导致API网关验证源是否在允许的源列表中。对于“null”,这通常不是这种情况(因为不推荐这样做),导致API网关以HTTP 403禁止拒绝请求。
为什么不直接允许null作为CORS源?
(注:此处原文未给出答案,以下为可能的解释)
直接允许null作为CORS源可能会导致安全问题,因为CORS策略的一个重要部分是限制哪些域可以与您的服务进行交互。允许null源可能会导致任何域都可以与您的服务进行交互,这可能会打开跨站点请求伪造(CSRF)攻击的大门。因此,为了保持安全性,通常不建议将null作为允许的CORS源。允许将’null‘作为’Access-Control-Allow-Origin‘是不推荐的,因为这会引入多个安全问题。更多信息请参见https://w3c.github.io/webappsec-cors-for-developers/#avoid-returning-access-control-allow-origin-null 。
为什么不直接允许所有来源/方法用于CORS?
虽然允许所有来源/方法用于CORS可以防止问题,但它也会引入重大的安全问题。CORS设置应始终尽可能限制性,特别是在涉及敏感数据(如凭据)时。详情请参见https://stackoverflow.com/a/56457665 。
Keycloak不应该修复这个问题吗?
我们建议Keycloak使’Referrer-Policy‘的值可配置,就像他们允许某些其他头部一样。
以下是在2020年10月为此问题创建的Keycloak错误报告(不是我们创建的),建议正是如此:https://issues.redhat.com/browse/KEYCLOAK-16032
不知何故,Keycloak的开发人员在错误线程中混淆了,并以“’null‘是’origin‘的有效值”(这并不是问题)为由将其标记为“已解释”,而不是实际解决问题(例如,通过使’Referrer-Policy‘可自定义,类似于其他HTTP响应头部值)。
这个解决方案不会降低整体安全性吗?
确实,’no-referrer‘比例如’same-origin‘的’Referrer-Policy‘更为严格,因为它导致浏览器在’Referer‘和’origin‘头部向API网关发送更少的细节。
然而,结合使用CORS启用的API网关,使用’no-referrer‘从系统级别完全破坏了Keycloak,使其完全无用。因此,使用此设置提供的安全性和功能性与关闭Keycloak相同。
为了使这与启用CORS的API网关一起工作,浏览器必须发送更多细节,特别是对于同一来源的请求,为’origin‘头部提供一个适当的值。这可以通过使用不同的’Referrer-Policy‘来实现,例如’same-origin’。
对我们来说,这是一个可以接受的(非常有限的)安全性降低,至少拥有预期的功能;此外,这些细节只影响同一来源,这完全在我们的控制之下。
我可以使用其他Referrer-Policy值吗?
你可以选择一个适合你设置的referrer-policy,没有要求必须使用’same-origin’。例如,典型的浏览器默认值’strict-origin-when-cross-origin‘也可以正常工作。