Идиоматический способ запроса HTTP Basic Auth in Go?
Я довольно свежий, чтобы пойти и уже любить его. Я создаю REST API с использованием Gorilla mux в качестве маршрутизатора.
Мне интересно, как я могу защитить определенные маршруты с помощью простого HTTP Basic Auth. Мне не нужно читать учетные данные из файла или любого внешнего источника, я просто хочу защитить выбранные маршруты жестко закодированным именем пользователя и паролем HTTP Basic Auth.
Каков идиоматический способ сделать это в Go? Горилла предлагает что-нибудь, чтобы сделать его более легким? Если бы вы могли предоставить несколько строк кода, это было бы просто замечательно.
Ответы
Ответ 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)
}
}