英文:
Unable to protect gorilla/mux Subroute with basic auth
问题
我正在尝试使用gorilla/mux
创建路由,其中一些路由应该受基本身份验证保护,而其他路由则不需要。具体来说,/v2
下的每个路由都应该需要基本身份验证,但是/health
下的路由应该是公开可访问的。
如下所示,我可以使用BasicAuth()
包装每个/v2
路由处理程序,但这违反了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))
}
}
英文:
I am trying to create routes using gorilla/mux
, some of which should be protected by basic auth and others shouldn't. Specifically, every route under /v2
should require basic auth, but the routes under /health
should be publicly accessible.
As you can see below, I can wrap each of my /v2
route handlers with BasicAuth()
, but that's against the DRY principle, and also error prone, not to mention the security implications of forgetting to wrap one of those handlers.
I have the following output from curl
. All but the last one is as I expect. One should not be able to GET
/smallcat
without authentication.
$ 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"
Here's the complete code. I believe I need to fix the v2Router
definition somehow, but fail to see how.
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))
}
}
答案1
得分: 1
我通过使用negroni
实现了预期的行为。如果BasicAuth()
调用失败,则不会调用/v2
下的任何路由处理程序。
工作代码在此处的Gist中(有修订版,供感兴趣的人使用):https://gist.github.com/gurjeet/13b2f69af6ac80c0357ab20ee24fa575
根据SO的约定,以下是完整的代码:
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))
}
}
英文:
I achieved the expected behavior by using negroni
. If the BasicAuth()
call fails, none of the route handlers under /v2
are invoked.
The working code is in a Gist (with revisions, for those interested) here: https://gist.github.com/gurjeet/13b2f69af6ac80c0357ab20ee24fa575
Per SO convention, though, here's the complete code:
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))
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论