Jak ludzie zarządzają uwierzytelnianiem w Go? [Zamknięte]

187

W przypadku osób budujących interfejsy API RESTful i aplikacje front-endowe JS w Go, jak zarządzasz uwierzytelnianiem? Czy korzystasz z konkretnych bibliotek lub technik?

Jestem zaskoczony, że tak mało dyskusji na ten temat. Pamiętam o następujących odpowiedziach i staram się unikać opracowywania własnej implementacji:

Formularz uwierzytelnienia w ASP.Net

Czy każdy koduje własne rozwiązanie osobno?

SexxLuthor
źródło
5
Uwierzytelnianie zależy w dużej mierze od typu aplikacji, której szukasz. Nie ma jednego uniwersalnego rozwiązania. Ponadto jest to trudny problem do rozwiązania. Prawdopodobnie dlatego nie znajdziesz żadnej rozstrzygającej dokumentacji.
jimt
21
Hej, dzięki za szybką odpowiedź. Zrozumiano, ale większość języków i struktur opracowała rozwiązania uwierzytelniające, które spełniają najczęstsze wymagania uwierzytelniania wspólne dla większości aplikacji oraz mają szeroki udział społeczności i wsparcie. Zgadzam się, że to trudny problem. Czy ci nie czerpią największych korzyści ze współpracy? (To nie jest skarga, ponieważ jest to oprogramowanie typu open source, ale bardziej spostrzeżenie, że wszyscy odkrywamy na nowo koło. :)
SexxLuthor
13
@jimt Fakt, że jest to trudny problem, sprawia, że ​​jeszcze ważniejsze jest dostarczanie śmiertelnikom rozwiązania stożkowego, którego nie możemy się pomylić.
tymtam,
Głosuję za zamknięciem tego pytania jako nie na temat, ponieważ jest to pytanie ankietowe.
Flimzy,

Odpowiedzi:

115

To pytanie zyskuje mnóstwo wyświetleń - i ma znaczek Popularnego pytania - więc wiem, że zainteresowanie tym tematem jest bardzo ukryte, a wiele osób pyta dokładnie o to samo i nie znajduje odpowiedzi na Interwebs.

Większość dostępnych informacji skutkuje tekstowym odpowiednikiem falistej ręki, pozostawionej jako „ćwiczenie dla czytelnika”. ;)

W końcu jednak znalazłem jeden konkretny przykład (hojnie) podany przez członka listy mailingowej golang-nuts:

https://groups.google.com/forum/#!msg/golang-nuts/GE7a_5C5kbA/fdSnH41pOPYJ

Zapewnia to sugerowany schemat i implementację po stronie serwera jako podstawę do niestandardowego uwierzytelnienia. Kod po stronie klienta nadal zależy od Ciebie.

(Mam nadzieję, że autor postu zobaczy to: Dzięki!)

Fragment (i sformatowany):


„Sugerowałbym coś w rodzaju następującego projektu:

create table User (
 ID int primary key identity(1,1),
 Username text,
 FullName text,
 PasswordHash text,
 PasswordSalt text,
 IsDisabled bool
)

create table UserSession (
 SessionKey text primary key,
 UserID int not null, -- Could have a hard "references User"
 LoginTime <time type> not null,
 LastSeenTime <time type> not null
)
  • Gdy użytkownik loguje się do Twojej witryny za pomocą testu POST w ramach TLS, sprawdź, czy hasło jest prawidłowe.
  • Następnie wydaj losowy klucz sesji, powiedz 50 lub więcej znaków kryptograficznych i włóż je do bezpiecznego pliku cookie.
  • Dodaj ten klucz sesji do tabeli UserSession.
  • Następnie, gdy ponownie zobaczysz tego użytkownika, najpierw uderz w tabelę UserSession, aby sprawdzić, czy SessionKey jest tam z prawidłowym LoginTime i LastSeenTime, a użytkownik nie zostanie usunięty. Możesz go zaprojektować, aby zegar automatycznie usuwał stare wiersze w sesji użytkownika. ”
SexxLuthor
źródło
8
Zwykle podoba nam się samodzielna strona tutaj w SO, więc czy mógłbyś opublikować rozwiązanie tutaj? Na wypadek gdyby link zmienił się w odpowiednim czasie (zgnilizna linku i co jeszcze ...) Przyszli goście mogą być z tego zadowoleni.
topskip
To uczciwe pytanie, z szacunkiem postawione. Dziękuję Ci. Zawarłem rozwiązanie; czy uważasz, że należy również podać nazwisko autora? (To jest publiczne, ale zastanawiam się nad etykietą każdej z opcji.)
SexxLuthor
Myślę, że tak jest dobrze. Nie twierdzisz się, że jesteś „właścicielem” tego fragmentu i nie widzę, aby jego oryginalny autor wymagał przypisania każdej kopii. (Tylko moje dwa centy).
topskip
35
Baza danych nie powinna zawierać pola „PasswordSalt”, ponieważ powinieneś używać bcrypt jako algorytmu mieszającego, który automatycznie tworzy sól i włącza ją do zwracanego skrótu. Użyj również funkcji porównywania stałego czasu.
0xdabbad00
4
+1 za bcrypt. Ponadto sesje goryla z kluczami „szyfrowania” i „uwierzytelniania” pozwolą bezpiecznie przechowywać informacje o sesji bez korzystania z tabeli DB.
crantok
14

Do uwierzytelnienia użyłbyś oprogramowania pośredniego.

Możesz wypróbować go-http-auth dla podstawowego i podsumowania uwierzytelniania i gomniauth dla OAuth2.

Ale sposób uwierzytelnienia zależy od Twojej aplikacji.

Uwierzytelnianie wprowadza stan / kontekst do twoich http.Handlerów i ostatnio dyskutowano o tym.

Dobrze znanymi rozwiązaniami problemu kontekstu są opisane tutaj goryl / kontekst i kontekst google .

Zrobiłem bardziej ogólne rozwiązanie bez potrzeby globalnego stanu w go-on / wrap, którego można używać razem lub bez dwóch pozostałych i ładnie integruje się z bez kontekstowym oprogramowaniem pośrednim.

wraphttpauth zapewnia integrację go-http-auth z go-on / wrap.

metakeule
źródło
Jest wiele nowych rzeczy dla początkujących. Zastanawiam się, jakie rzeczy powinien zacząć od początkującego. go-http-authlub gomniauthoba z nich?
Casper
Czy ktoś tutaj zaimplementował OAuth 1.0 w Golang? Uwierzytelnianie na podstawie klucza klienta i tajnego?
user2888996
Jak mogę wdrożyć oAuth 1.0? Używasz klucza klienta i tajnego klucza? Proszę pomóż. Nie dostaję żadnej biblioteki do tego samego.
user2888996
9

Odpowiadając na to w 2018 roku. Sugeruję użycie JWT (JSON Web Token). Odpowiedź oznaczona jako rozwiązana ma tę wadę, że jest to podróż, którą wykonał z przodu (użytkownik) i z powrotem (serwer / db). Co gorsza, jeśli użytkownik często wysyła żądanie wymagające uwierzytelnienia, spowoduje to nadmierne żądanie z / do serwera i bazy danych. Aby rozwiązać ten problem, użyj JWT, który przechowuje token na końcu użytkownika, z którego użytkownik może korzystać w dowolnym momencie, gdy potrzebuje dostępu / żądania. Nie ma potrzeby podróży do bazy danych i przetwarzania serwera, aby sprawdzić ważność tokena zajmuje krótko.

mfathirirhas
źródło
6

Innym pakietem typu open source do obsługi uwierzytelniania za pomocą plików cookie jest httpauth .

(tak przy okazji, napisane przeze mnie)

Cameron Little
źródło
2

Szczerze mówiąc, istnieje wiele metod i technik uwierzytelniania, które można zamontować w aplikacji i które zależą od logiki biznesowej i wymagań aplikacji.
Na przykład Oauth2, LDAP, lokalne uwierzytelnianie itp.
Moja odpowiedź zakłada, że ​​szukasz lokalnego uwierzytelnienia, co oznacza, że ​​zarządzasz tożsamościami użytkownika w aplikacji. Serwer musi udostępniać zestaw zewnętrznych interfejsów API, umożliwiających użytkownikom i administratorom zarządzanie kontami oraz sposób, w jaki chcą się identyfikować na serwerze, aby uzyskać wiarygodną komunikację. w końcu utworzysz tabelę DB zawierającą informacje o użytkowniku. gdzie hasło jest mieszane ze względów bezpieczeństwa Zobacz Jak przechowywać hasło w bazie danych

przyjmijmy wymagania aplikacji do uwierzytelnienia użytkowników na podstawie jednej z następujących metod:

  • podstawowe uwierzytelnianie (nazwa użytkownika, hasło):
    Ta metoda autoryzacji zależy od zestawu danych uwierzytelniających użytkownika w nagłówku autoryzacji zakodowanym w base64 i zdefiniowanym w rfc7617 , w zasadzie, gdy aplikacja otrzyma żądanie użytkownika, dekoduje autoryzację i ponownie hashuje hasło, aby porównać je w DB skrót, jeśli jest zgodny, użytkownik uwierzytelniony, w przeciwnym razie zwraca kod stanu 401 użytkownikowi.

  • uwierzytelnianie oparte na certyfikatach:
    ta metoda uwierzytelniania zależy od certyfikatu cyfrowego w celu identyfikacji użytkownika i jest znana jako uwierzytelnianie x509, więc gdy aplikacja odbierze żądanie użytkownika, odczytuje certyfikat klienta i weryfikuje, czy jest on zgodny z podanym certyfikatem głównym CA do aplikacji.

  • token na okaziciela:
    ta metoda uwierzytelniania zależy od krótkoterminowych tokenów dostępu, token na okaziciela jest szyfrowanym ciągiem, zwykle generowanym przez serwer w odpowiedzi na żądanie logowania. więc gdy aplikacja otrzyma żądanie użytkownika, odczyta autoryzację i zweryfikuje token w celu uwierzytelnienia użytkownika.

Jednak polecam go-guardian dla biblioteki uwierzytelniania, która robi to poprzez rozszerzalny zestaw metod uwierzytelniania zwanych strategiami. w zasadzie Go-Guardian nie montuje tras ani nie zakłada żadnego konkretnego schematu bazy danych, co maksymalizuje elastyczność i umożliwia podejmowanie decyzji przez programistę.

Utworzenie uwierzytelniającego go-guardian jest proste.

Oto pełny przykład powyższych metod.

package main

import (
    "context"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "sync"

    "github.com/golang/groupcache/lru"
    "github.com/gorilla/mux"
    "github.com/shaj13/go-guardian/auth"
    "github.com/shaj13/go-guardian/auth/strategies/basic"
    "github.com/shaj13/go-guardian/auth/strategies/bearer"
    gx509 "github.com/shaj13/go-guardian/auth/strategies/x509"
    "github.com/shaj13/go-guardian/store"
)

var authenticator auth.Authenticator
var cache store.Cache

func middleware(next http.Handler) http.HandlerFunc {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("Executing Auth Middleware")
        user, err := authenticator.Authenticate(r)
        if err != nil {
            code := http.StatusUnauthorized
            http.Error(w, http.StatusText(code), code)
            return
        }
        log.Printf("User %s Authenticated\n", user.UserName())
        next.ServeHTTP(w, r)
    })
}

func Resource(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Resource!!\n"))
}

func Login(w http.ResponseWriter, r *http.Request) {
    token := "90d64460d14870c08c81352a05dedd3465940a7"
    user := auth.NewDefaultUser("admin", "1", nil, nil)
    cache.Store(token, user, r)
    body := fmt.Sprintf("token: %s \n", token)
    w.Write([]byte(body))
}

func main() {
    opts := x509.VerifyOptions{}
    opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
    opts.Roots = x509.NewCertPool()
    // Read Root Ca Certificate
    opts.Roots.AddCert(readCertificate("<root-ca>"))

    cache = &store.LRU{
        lru.New(100),
        &sync.Mutex{},
    }

    // create strategies
    x509Strategy := gx509.New(opts)
    basicStrategy := basic.New(validateUser, cache)
    tokenStrategy := bearer.New(bearer.NoOpAuthenticate, cache)

    authenticator = auth.New()
    authenticator.EnableStrategy(gx509.StrategyKey, x509Strategy)
    authenticator.EnableStrategy(basic.StrategyKey, basicStrategy)
    authenticator.EnableStrategy(bearer.CachedStrategyKey, tokenStrategy)

    r := mux.NewRouter()
    r.HandleFunc("/resource", middleware(http.HandlerFunc(Resource)))
    r.HandleFunc("/login", middleware(http.HandlerFunc(Login)))

    log.Fatal(http.ListenAndServeTLS(":8080", "<server-cert>", "<server-key>", r))
}

func validateUser(ctx context.Context, r *http.Request, userName, password string) (auth.Info, error) {
    // here connect to db or any other service to fetch user and validate it.
    if userName == "stackoverflow" && password == "stackoverflow" {
        return auth.NewDefaultUser("stackoverflow", "10", nil, nil), nil
    }

    return nil, fmt.Errorf("Invalid credentials")
}

func readCertificate(file string) *x509.Certificate {
    data, err := ioutil.ReadFile(file)

    if err != nil {
        log.Fatalf("error reading %s: %v", file, err)
    }

    p, _ := pem.Decode(data)
    cert, err := x509.ParseCertificate(p.Bytes)
    if err != nil {
        log.Fatalf("error parseing certificate %s: %v", file, err)
    }

    return cert
}

Stosowanie:

  • Uzyskaj token:
curl  -k https://127.0.0.1:8080/login -u stackoverflow:stackoverflow
token: 90d64460d14870c08c81352a05dedd3465940a7
  • Uwierzytelnij się za pomocą tokena:
curl  -k https://127.0.0.1:8080/resource -H "Authorization: Bearer 90d64460d14870c08c81352a05dedd3465940a7"

Resource!!
  • Uwierzytelnij się przy użyciu poświadczenia użytkownika:
curl  -k https://127.0.0.1:8080/resource -u stackoverflow:stackoverflow

Resource!!
  • Uwierzytelnij się za pomocą certyfikatu użytkownika:
curl --cert client.pem --key client-key.pem --cacert ca.pem https://127.0.0.1:8080/resource

Resource!!

Możesz włączyć wiele metod uwierzytelniania jednocześnie. Zwykle powinieneś użyć co najmniej dwóch metod

shaj13
źródło
1

Spójrz na Labstack Echo - otacza uwierzytelnianie dla interfejsów API RESTful i aplikacji frontendowych w oprogramowaniu pośrednim, którego można używać do ochrony określonych tras API.

Na przykład skonfigurowanie podstawowego uwierzytelnienia jest tak proste, jak utworzenie nowego podrzędu dla /admintrasy:

e.Group("/admin").Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
    if username == "joe" && password == "secret" {
        return true, nil
    }
    return false, nil
}))

Zobacz wszystkie opcje uwierzytelniania oprogramowania pośredniego Labstack tutaj.

Adil B.
źródło