我正在 Golang 中构建一个 Web 应用程序,使用 mux 作为路由器,使用 Negroni 管理 API 的中间件。
我有以下中间件:
- 身份验证中间件检查客户端是否具有有效会话,如果没有则返回 401
- 允许基于https://github.com/rs/cors/的编辑版本的 CORS 请求的中间件
我想酌情将中间件应用于特定的路由,即在所有请求上使用 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")
}
}