go - 无法使用基本身份验证保护 gorilla/mux 子路由

标签 go basic-authentication router gorilla mux

我正在尝试使用 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))
    }
}

最佳答案

我通过使用 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))
    }
}

关于go - 无法使用基本身份验证保护 gorilla/mux 子路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42321789/

相关文章:

java - 使用cling进行路由器端口转发

javascript - angular2中多步表单每一步的唯一url

for-loop - 转到如何正确使用 for ... range 循环

mongodb - Mgo 插入命令不创建数据库或插入文档

docker - 如何在 Elastic Beanstalk Docker 环境中配置 HTTP 基本身份验证?

asp.net-mvc-4 - MVC WebApi + 基础认证 + JSONP跨域

rest - 具有基本身份验证 XMLHttpRequest 的 Tomcat7 REST 服务

file - 如何检查Go中是否存在文件?

go - 使用 Viper Go 读取环境变量

php - 如何从 joomla url 中隐藏 id