2017-02-18 25 views
0

Я пытаюсь создать маршруты, используя gorilla/mux, некоторые из которых должны быть защищены базовым auth, а другие не должны. В частности, для каждого маршрута под кодом /v2 требуется базовое auth, но маршруты под /health должны быть общедоступными.Невозможно защитить gorilla/mux Subroute с базовым auth

Как вы можете видеть ниже, я могу обернуть каждый из моих обработчиков маршрутов /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:[email protected]: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

Приложение оборачивает обработчик/v2 только. Оберните каждый обработчик. –

+0

Пакеты 'goji/httpauth' утверждают, что защищают все маршруты только одной оберткой вокруг корневого обработчика. См. Их пример в https://github.com/goji/httpauth#gorillamux. Я попытался использовать этот пакет для защиты '/ v2' тоже, но без каких-либо успехов. –

+0

Возможно, вам понадобится что-то вроде промежуточного слоя для выполнения такой работы: https://github.com/urfave/negroni – ymonad

ответ

0

Я достиг ожидаемого поведения, используя negroni. Если вызов BasicAuth() завершается с ошибкой, ни один из обработчиков маршрутов под /v2 не вызывается.

Рабочий кода находится в Сущности (с изменениями, для тех, кто заинтересован) здесь: https://gist.github.com/gurjeet/13b2f69af6ac80c0357ab20ee24fa575

Per 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)) 
    } 
}