我正在尝试使用gorilla/mux
创建路由,其中一些应该受到基本身份验证的保护,而其他人则不应该。具体而言,/v2
下的每条路线都需要基本身份验证,但/health
下的路由应该可以公开访问。
正如您在下面所看到的,我可以使用/v2
包装每个BasicAuth()
路由处理程序,但这违反了DRY原则,并且也容易出错,更不用说安全性了忘记包装其中一个处理程序的含义。
我有curl
的以下输出。除了最后一个之外的所有内容都如我所料。如果没有身份验证,我们就无法GET
/smallcat
。
$ curl localhost:3000/health/ping
"PONG"
$ curl localhost:3000/health/ping/
404 page not found
$ curl localhost:3000/v2/bigcat
Unauthorised.
$ curl apiuser:apipass@localhost:3000/v2/bigcat
"Big MEOW"
$ curl localhost:3000/v2/smallcat
"Small Meow"
这里是完整的代码。我相信我需要以某种方式修复v2Router
定义,但却看不清楚。
package main
import (
"crypto/subtle"
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
func endAPICall(w http.ResponseWriter, httpStatus int, anyStruct interface{}) {
result, err := json.MarshalIndent(anyStruct, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(httpStatus)
w.Write(result)
}
func BasicAuth(handler http.HandlerFunc, username, password, realm string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorised.\n"))
return
}
handler(w, r)
}
}
func routers() *mux.Router {
username := "apiuser"
password := "apipass"
noopHandler := func(http.ResponseWriter, *http.Request) {}
topRouter := mux.NewRouter().StrictSlash(false)
healthRouter := topRouter.PathPrefix("/health/").Subrouter()
v2Router := topRouter.PathPrefix("/v2").HandlerFunc(BasicAuth(noopHandler, username, password, "Provide username and password")).Subrouter()
healthRouter.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "PONG")
})
v2Router.HandleFunc("/smallcat", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Small Meow")
})
bigMeowFn := func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Big MEOW")
}
v2Router.HandleFunc("/bigcat", BasicAuth(bigMeowFn, username, password, "Provide username and password"))
return topRouter
}
func main() {
if r := routers(); r != nil {
log.Fatal("Server exited:", http.ListenAndServe(":3000", r))
}
}
答案 0 :(得分:0)
我使用negroni
达到了预期的行为。如果BasicAuth()
调用失败,则/v2
下的所有路由处理程序都不会被调用。
工作代码在Gist中(对于那些感兴趣的人有修改):https://gist.github.com/gurjeet/13b2f69af6ac80c0357ab20ee24fa575
按照惯例,这里是完整的代码:
package main
import (
"crypto/subtle"
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/urfave/negroni"
)
func endAPICall(w http.ResponseWriter, httpStatus int, anyStruct interface{}) {
result, err := json.MarshalIndent(anyStruct, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(httpStatus)
w.Write(result)
}
func BasicAuth(w http.ResponseWriter, r *http.Request, username, password, realm string) bool {
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorised.\n"))
return false
}
return true
}
func routers() *mux.Router {
username := "apiuser"
password := "apipass"
v2Path := "/v2"
healthPath := "/health"
topRouter := mux.NewRouter().StrictSlash(true)
healthRouter := mux.NewRouter().PathPrefix(healthPath).Subrouter().StrictSlash(true)
v2Router := mux.NewRouter().PathPrefix(v2Path).Subrouter().StrictSlash(true)
healthRouter.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "PONG")
})
v2Router.HandleFunc("/smallcat", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Small Meow")
})
bigMeowFn := func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Big MEOW")
}
v2Router.HandleFunc("/bigcat", bigMeowFn)
topRouter.PathPrefix(healthPath).Handler(negroni.New(
/* Health-check routes are unprotected */
negroni.Wrap(healthRouter),
))
topRouter.PathPrefix(v2Path).Handler(negroni.New(
negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if BasicAuth(w, r, username, password, "Provide user name and password") {
/* Call the next handler iff Basic-Auth succeeded */
next(w, r)
}
}),
negroni.Wrap(v2Router),
))
return topRouter
}
func main() {
if r := routers(); r != nil {
log.Fatal("Server exited:", http.ListenAndServe(":3000", r))
}
}