Spring WebSocket:Credentials设为False时XMLHttpRequest与Fetch的CORS行为差异
问题描述
我在Spring WebSocket应用中将Credentials配置为false,但请求http://192.168.7.175/api/webSocketEndPoint/info?t=17139606时,尽管响应状态为200,仍出现CORS错误,日志报错如下:
Access to XMLHttpRequest at 'http://192.168.7.175/api/webSocketEndPoint/info?t=1713960686964' from origin 'http://192.168.7.175:9092' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
奇怪的是,使用fetch('http://192.168.7.175/api/webSocketEndPoint/info?t=17139606')请求时,返回200状态且无CORS错误。我的服务器基于Spring WebSocket实现,客户端使用sockJs发起请求。
请问两种请求方式的CORS行为差异是什么原因导致的?
补充疑问:我们不能将allow-credentials设为true,因为会引发CSRF攻击,对吗?
问题解答
一、两种请求的CORS行为差异原因
SockJS/XMLHttpRequest 的默认行为
SockJS内部用XMLHttpRequest发起请求时,会自动把withCredentials设为true(即请求的credentials mode为include)。此时浏览器会强制检查响应头的Access-Control-Allow-Credentials是否为true,但你服务器配置的是false,导致响应头里该字段为空或不存在,浏览器因此触发CORS拦截。fetch 的默认行为
fetch API默认的credentials mode是same-origin,只有请求目标和当前页面同源时才会携带凭证。你的请求跨了端口(9092到默认端口),属于跨域场景,所以fetch默认不会携带凭证,也就不会要求响应头包含Access-Control-Allow-Credentials: true,因此浏览器不会拦截这个请求。如果给fetch显式加上credentials: 'include'参数,你会看到和SockJS完全一样的CORS错误。
二、关于allow-credentials与CSRF的疑问
- 首先明确:不是开启
allow-credentials就一定会引发CSRF攻击,风险取决于应用场景和防护措施:- 如果你的WebSocket接口不需要依赖Cookie等凭证做身份验证,确实没必要开启
allow-credentials,保持false即可。 - 如果必须携带凭证,只要做好CSRF防护(比如Spring Security的CSRF令牌校验,或者针对WebSocket请求做专门的CSRF验证),就能有效降低风险。
- 如果你的WebSocket接口不需要依赖Cookie等凭证做身份验证,确实没必要开启
- 回到你的场景,既然服务器配置了
Credentials=false,那要确保客户端不发送带凭证的请求。可以在SockJS客户端初始化时,显式设置withCredentials: false,这样XMLHttpRequest就不会以include模式发起请求,浏览器也就不会检查Access-Control-Allow-Credentials头,CORS错误就会消失。
内容的提问来源于stack exchange,提问作者sadeq shahmoradi

