http4s 0.23.33版本CORS中间件未返回Access-Control-Allow-Origin头问题
http4s 0.23.33 CORS预检请求缺失
Access-Control-Allow-Origin头问题 问题现象
- 使用http4s 0.23.33版本,配置CORS中间件后,预检OPTIONS请求未返回
Access-Control-Allow-Origin头,导致前端JavaScript请求被浏览器拦截。 - 用curl发送预检请求,响应中同样缺少该头。
- 改用
withAllowOriginAll+withAllowCredentials(false)配置时,CORS头能正常返回,但前端请求带credentials: "include"会被浏览器阻止,无法存储Cookie。
当前CORS配置
private val corsMiddleware = CORS.policy .withAllowOriginHost(Set( Origin.Host(Uri.Scheme.http, Uri.RegName("localhost"), Some(8080)) )) .withAllowHeadersAll .withAllowMethodsAll .withAllowCredentials(true) // 允许浏览器存储Cookie .withMaxAge(1.day) val httpApp = corsMiddleware(AutoSlash(loggerService(errorLogger(routes)))).orNotFound
curl预检请求测试
命令:
curl -i -X OPTIONS http://localhost:4041/auth/login \ -H "Origin: http://localhost:8080" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type"
响应:
HTTP/1.1 200 OK Date: Sun, 31 May 2026 18:23:08 GMT Connection: keep-alive Vary: Origin Content-Length: 0
Auth路由代码
val publicRoutes: HttpRoutes[F] = HttpRoutes.of[F]{ case req @ POST -> Root / "login" => for { loginRequest <- req.as[LoginRequest] result <- Logger[F].info("hello world---") *> authService.login(loginRequest) resp <- result match { case Left(error) => Logger[F].warn("login failed") Unauthorized( `WWW-Authenticate`(Challenge("Bearer", "Resources")), Json.obj( "Error" -> Json.fromString(error.toString) ) ) case Right(t) => Ok(s"Access-token: ${t.accessToken}") .map(r => CookieService.setRefreshCookie(r, t.refreshToken, config) ) } } yield resp }
特殊配置测试结果
使用以下配置时,CORS头正常返回,但无法使用凭证:
private val corsMiddleware = CORS.policy .withAllowOriginAll .withAllowHeadersAll .withAllowMethodsAll .withAllowCredentials(false) .withMaxAge(1.day)
响应头:
Connection: keep-alive Access-Control-Allow-Origin: * Access-Control-Allow-Methods: * Access-Control-Allow-Headers: * Access-Control-Max-Age: 86400 Content-Length: 0
前端测试代码(localhost:8080部署)
<!doctype html> <html> <script> async function fetchdata() { try { const response = await fetch( "http://localhost:4041/auth/login", { method: "POST", credentials: "include", // 允许存储/发送Cookie headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: "test@test.com", password: "test123" }) } ); console.log("Status:", response.status); console.log("Headers:", [...response.headers.entries()]); if (!response.ok) { const errorText = await response.text(); console.error("Login failed:", errorText); return; } const data = await response.json(); console.log("Access Token:", data.accessToken); console.log("Refresh Token:", data.refreshToken); console.log("Full Response:", data); } catch (err) { console.error("Request failed:", err); } } </script> <button onclick="fetchdata();"> Fetch </button> </html>
解决方案
问题源于http4s 0.23.x版本CORS中间件对withAllowOriginHost的处理bug,改用直接匹配完整Origin的方式即可修复:
修复步骤
- 替换匹配方式:放弃
withAllowOriginHost,改用withAllowOrigin直接匹配完整的Origin字符串:
private val corsMiddleware = CORS.policy .withAllowOrigin(Set(Uri.fromString("http://localhost:8080").right.get)) .withAllowHeadersAll .withAllowMethodsAll .withAllowCredentials(true) .withMaxAge(1.day)
确认中间件顺序:确保CORS中间件是最外层包裹,避免其他中间件拦截修改响应头(你的原顺序正确,无需调整)。
避免手动处理OPTIONS请求:http4s CORS中间件会自动处理预检请求,不要在路由中手动添加OPTIONS逻辑,否则会绕过中间件处理。
验证修复
重新用curl测试预检请求,响应头应包含:
Access-Control-Allow-Origin: http://localhost:8080 Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: * Access-Control-Allow-Headers: * Access-Control-Max-Age: 86400
此时前端带credentials: "include"的请求可正常执行,Cookie也能被正确存储。
内容的提问来源于stack exchange,提问作者unknown anonymous
相关产品推荐
相关产品推荐

