You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的方式即可修复:

修复步骤

  1. 替换匹配方式:放弃withAllowOriginHost,改用withAllowOrigin直接匹配完整的Origin字符串:
private val corsMiddleware = CORS.policy
  .withAllowOrigin(Set(Uri.fromString("http://localhost:8080").right.get))
  .withAllowHeadersAll
  .withAllowMethodsAll
  .withAllowCredentials(true)
  .withMaxAge(1.day)
  1. 确认中间件顺序:确保CORS中间件是最外层包裹,避免其他中间件拦截修改响应头(你的原顺序正确,无需调整)。

  2. 避免手动处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 07:59:49