1

我正在 Golang 中构建一个 Web 应用程序,使用 mux 作为路由器,使用 Negroni 管理 API 的中间件。

我有以下中间件:

我想酌情将中间件应用于特定的路由,即在所有请求上使用 CORS 中间件并仅在受保护的路由上检查身份验证。

当我尝试使用前端发出 API 请求时,实际发生的情况是,CORS 中间件似乎只在每个 API 路由的初始 OPTIONS 请求中被调用,并且随后没有被使用。

例如:

  • UI 使用包含凭据的 JSON 对象对 /api/login 进行 POST
  • 在浏览器控制台中,我们看到初始 preflight OPTIONS 请求获得了 200 OK,其中我们使用 corsMiddleware 中的 handlePreflight 设置了所有标头,到目前为止一切顺利。
  • 然后后续的 POST 请求都没有命中 corsMiddleware。我们知道中间件永远不会被调用,因为我们从未在服务器日志中看到它被调用过。我们在 Chrome 中收到错误“请求的资源上不存在‘Access-Control-Allow-Origin’标头”。
  • 奇怪的实际上调用了函数 handleLogin,我们可以看到正确的凭据,并且服务器似乎正确地为它们存储了会话。

我们还在其他一些路由上看到了奇怪的东西——即当向 /api/entities 发出 Postman GET 请求时,我们能够在未登录时获取实体列表(应该受到保护)。就好像身份验证中间件一样没有被调用或行为不符合预期。

我对其中一些概念相当陌生,所以也许我误解了一些东西。任何帮助,将不胜感激。

我的代码如下(main.go):

router := mux.NewRouter()
router.HandleFunc("/", ServeUI).Methods("GET")

apiRouter := router.PathPrefix("/api").Subrouter()

authRouter := apiRouter.PathPrefix("/auth").Subrouter()
authRouter.HandleFunc("/login", HandleLogin).Methods("POST")
authRouter.HandleFunc("/logout", HandleLogout).Methods("POST")

entitiesRouter := apiRouter.PathPrefix("/entities").Subrouter()
entitiesRouter.HandleFunc("/", GetEntities).Methods("GET")

commonAPIMiddleware := negroni.New(corsMiddleware.NewCorsMiddleware())

router.PathPrefix("/api/auth").Handler(commonAPIMiddleware.With(
    negroni.Wrap(authRouter),
))

router.PathPrefix("/api/entities").Handler(commonAPIMiddleware.With(
    auth.NewAPIAuthMiddleware(),
    negroni.Wrap(entitiesRouter),
))

n := negroni.New(negronilogrus.NewMiddleware())
n.UseHandler(router)
n.Run(":8009")

corsMiddleware 的代码如下:

// CorsMiddleware allows CORS request for api routes
type CorsMiddleware struct {
}

// Negroni compatible interface
func (m *CorsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    log.Info("CORS MIDDLEWARE CALLED")
    if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
        log.Info("ServeHTTP: Preflight request")
        handlePreflight(w, r)
        // Preflight requests are standalone and should stop the chain as some other
        // middleware may not handle OPTIONS requests correctly. One typical example
        // is authentication middleware ; OPTIONS requests won't carry authentication
        // headers (see #1)

        w.WriteHeader(http.StatusOK)
    } else {
        log.Info("ServeHTTP: Actual request")
        handleActualRequest(w, r)
        next(w, r)
    }
}

// handlePreflight handles pre-flight CORS requests
func handlePreflight(w http.ResponseWriter, r *http.Request) {
    headers := w.Header()
    origin := r.Header.Get("Origin")

    if r.Method != http.MethodOptions {
        log.Info("  Preflight aborted: %s!=OPTIONS", r.Method)
        return
    }
    // Always set Vary headers
    // see https://github.com/rs/cors/issues/10,
    //     https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001
    headers.Add("Vary", "Origin")
    headers.Add("Vary", "Access-Control-Request-Method")
    headers.Add("Vary", "Access-Control-Request-Headers")

    if origin == "" {
        log.Info("  Preflight aborted: empty origin")
        return
    }
    headers.Set("Access-Control-Allow-Origin", origin)

    // Spec says: Since the list of methods can be unbounded, simply returning the method indicated
    // by Access-Control-Request-Method (if supported) can be enough
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    headers.Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, X-Requested-With")
    headers.Set("Access-Control-Allow-Credentials", "true")
    headers.Set("Access-Control-Max-Age", strconv.Itoa(1000))
    log.Info("  Preflight response headers: %v", headers)
}

// handleActualRequest handles simple cross-origin requests, actual request or redirects
func handleActualRequest(w http.ResponseWriter, r *http.Request) {
    log.Info("CORS HANDLING ACTUAL REQUEST")
    headers := w.Header()
    origin := r.Header.Get("Origin")

    if r.Method == http.MethodOptions {
        log.Info("  Actual request no headers added: method == %s", r.Method)
        return
    }
    // Always set Vary, see https://github.com/rs/cors/issues/10
    headers.Add("Vary", "Origin")
    if origin == "" {
        log.Info("  Actual request no headers added: missing origin")
        return
    }

    headers.Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, X-Requested-With")
    headers.Set("Access-Control-Allow-Credentials", "true")

    headers.Set("Access-Control-Allow-Origin", origin)

    if true {
        headers.Set("Access-Control-Allow-Credentials", "true")
    }
} 
4

0 回答 0