Подтвердить что ты не робот

Идиоматический способ запроса HTTP Basic Auth in Go?

Я довольно свежий, чтобы пойти и уже любить его. Я создаю REST API с использованием Gorilla mux в качестве маршрутизатора.

Мне интересно, как я могу защитить определенные маршруты с помощью простого HTTP Basic Auth. Мне не нужно читать учетные данные из файла или любого внешнего источника, я просто хочу защитить выбранные маршруты жестко закодированным именем пользователя и паролем HTTP Basic Auth.

Каков идиоматический способ сделать это в Go? Горилла предлагает что-нибудь, чтобы сделать его более легким? Если бы вы могли предоставить несколько строк кода, это было бы просто замечательно.

4b9b3361

Ответ 1

Проверить req.BasicAuth() https://golang.org/pkg/net/http/#Request.BasicAuth

Вы можете проверить это в своем обработчике или обернуть свой обработчик следующим образом:

func auth(fn http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    user, pass, _ := r.BasicAuth()
    if !check(user, pass) {
       http.Error(w, "Unauthorized.", 401)
       return
    }
    fn(w, r)
  }
}

Где

check(u, p string) bool 

- это функция, которую вам придется писать самостоятельно, исходя из того, как вы храните учетные данные. Теперь вы можете использовать:

auth(originalHandler)

Где бы вы ни проходили оригиналHandler раньше.

[edit: Стоит добавить, что ваша функция проверки должна быть устойчивой к атакам с боковым каналом, таким как временные атаки. Также сохраненные пароли должны быть хэшированы криптографически случайной солью. Также вы, вероятно, должны использовать OAuth вместо этого, и пусть установленный поставщик удостоверений беспокоится о безопасности паролей для вас.]

Ответ 2

Объединяя пару ответов с простой копией/вставкой:

// BasicAuth wraps a handler requiring HTTP basic auth for it using the given
// username and password and the specified realm, which shouldn't contain quotes.
//
// Most web browser display a dialog with something like:
//
//    The website says: "<realm>"
//
// Which is really stupid so you may want to set the realm to a message rather than
// an actual realm.
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)
    }
}

...

http.HandleFunc("/", BasicAuth(handleIndex, "admin", "123456", "Please enter your username and password for this site"))

Обратите внимание, что subtle.ConstantTimeCompare() по-прежнему зависит от длины, поэтому, возможно, злоумышленники могут определить длину имени пользователя и пароля, если вы это сделаете. Чтобы обойти это, вы можете использовать их или добавить фиксированную задержку.

Ответ 3

С 2016 года я предлагаю использовать этот ответ. В любом случае, оберните базовую аутентификацию HTTP в SSL, чтобы избежать отправки имени пользователя и пароля в виде обычного текста.


Просто оберните обработчик в другой обработчик и используйте заголовок WWW-Authorization для входящего запроса.

Пример (полная версия):

func checkAuth(w http.ResponseWriter, r *http.Request) bool {
    s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
    if len(s) != 2 { return false }

    b, err := base64.StdEncoding.DecodeString(s[1])
    if err != nil { return false }

    pair := strings.SplitN(string(b), ":", 2)
    if len(pair) != 2 { return false }

    return pair[0] == "user" && pair[1] == "pass"
}

yourRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    if checkAuth(w, r) {
        yourOriginalHandler.ServeHTTP(w, r)
        return
    }

    w.Header().Set("WWW-Authenticate", `Basic realm="MY REALM"`)
    w.WriteHeader(401)
    w.Write([]byte("401 Unauthorized\n"))
})

К сожалению, std. библиотека предлагает клиентский базовый auth, и поэтому вы должны сделайте это самостоятельно или используйте библиотеку, например этот.

Ответ 4

go-http-auth сделает это за вас. Он будет соответствовать, если вы используете net/http.

Ответ 5

Тип запроса net/http имеет вспомогательные функции для выполнения этого теста (проверено на этапе 1.7). Более простая версия nemo-ответа будет выглядеть так:

func basicAuthHandler(user, pass, realm string, next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if checkBasicAuth(r, user, pass) {
            next(w, r)
            return
        }

        w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
        w.WriteHeader(401)
        w.Write([]byte("401 Unauthorized\n"))
    }
}

func checkBasicAuth(r *http.Request, user, pass string) bool {
    u, p, ok := r.BasicAuth()
    if !ok {
        return false
    }
    return u == user && p == pass
}

затем просто создайте обработчик с помощью бизнес-логики и передайте его как аргумент next в basicAuthHandler, чтобы создать новый "завернутый" обработчик.

Ответ 6

Я понимаю, что опаздываю на вечеринку. Я просто пересматриваю HTTP Basic Authentication. Пройдя все ответы здесь, я на самом деле остановился на вариации решения Timmmm. Я даже сделал шаг дальше и добавил хеширование в соответствии с его предложением улучшить безопасность. Понял, что я мог бы также поделиться своим изменением кода.

userhash := hasher("admin")
passhash := hasher("$CrazyUnforgettablePassword?")
realm := "Please enter username and password"

http.HandleFunc("/", authHandler(indexHandler, userhash, passhash, realm))

// Above code should obviously be in main() along with the http listener, etc.

// hasher uses package "crypto/sha256"
func hasher(s string) []byte {
    val := sha256.Sum256([]byte(s))
    return val[:]
}

func authHandler(handler http.HandlerFunc, userhash, passhash []byte, realm string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        user, pass, ok := r.BasicAuth()
        if !ok || subtle.ConstantTimeCompare(hasher(user),
            userhash) != 1 || subtle.ConstantTimeCompare(hasher(pass), passhash) != 1 {
            w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
            http.Error(w, "Unauthorized.", http.StatusUnauthorized)
            return
        }
        handler(w, r)
    }
}