Como as pessoas gerenciam a autenticação no Go? [fechadas]


187

Para quem cria APIs RESTful e aplicativos front-end JS no Go, como você gerencia a autenticação? Você está usando alguma biblioteca ou técnica específica?

Estou surpreso ao encontrar tão pouca discussão sobre isso. Lembro-me de respostas como as seguintes e estou tentando evitar o desenvolvimento de minha própria implementação:

Formulário de autenticação no ASP.Net

Todo mundo está codificando sua própria solução, separadamente?


5
A autenticação depende muito do tipo de aplicativo que você procura. Não existe uma solução única para todos. Além disso, é um problema difícil de resolver. É provavelmente por isso que você não encontrará nenhuma documentação conclusiva.
— jimt

21
Ei, obrigado pela resposta rápida. Entendido, mas a maioria das linguagens e estruturas criou soluções de autenticação que cobrem os requisitos de autenticação mais comuns compartilhados pela maioria dos aplicativos e têm ampla participação e suporte da comunidade. Concordo que é um problema difícil. Essas pessoas não se beneficiam mais do esforço cooperativo? (Isto não é uma queixa, porque este é open source, mas mais de uma observação de que todos nós estamos reinventando a roda :).
— SexxLuthor

13
@ jimt O fato de ser um problema difícil torna ainda mais importante nos fornecer aos mortais uma solução cônica que não podemos errar.
— tymtam

Estou votando para encerrar esta questão como fora de tópico, porque é uma pergunta de pesquisa.
— `` Flimzy

Respostas:


115

Essa pergunta recebe muitas visualizações - e possui um selo de pergunta popular -, então eu sei que há muito interesse latente nesse tópico, e muitas pessoas estão perguntando exatamente a mesma coisa e não encontram respostas nas Interwebs.

A maioria das informações disponíveis resulta no equivalente textual da coisa ondulada à mão, deixada como um "exercício para o leitor". ;)

No entanto, finalmente localizei um exemplo concreto, (generosamente) fornecido por um membro da lista de discussão golang-nuts:

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

Isso fornece um esquema sugerido e implementação do lado do servidor como base para autenticação personalizada. O código do lado do cliente ainda depende de você.

(Espero que o autor do post veja isso: Obrigado!)

Extraído (e reformatado):


"Eu sugeriria algo como o seguinte design:

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
)
  • Quando um usuário efetua login no seu site por meio de um POST sob TLS, determine se a senha é válida.
  • Em seguida, emita uma chave de sessão aleatória, digamos 50 ou mais caracteres de criptografia e itens em um cookie seguro.
  • Adicione essa chave de sessão à tabela UserSession.
  • Em seguida, quando você vir esse usuário novamente, primeiro atinja a tabela UserSession para verificar se o SessionKey está lá com um LoginTime válido e LastSeenTime e User não é excluído. Você pode projetá-lo para que um timer apague automaticamente as linhas antigas na UserSession. "

8
Tendemos a gostar de um site independente aqui na SO, então você se importaria de postar a solução aqui também? Apenas no caso de o link mudar no devido tempo (podridão do link e o que mais ...) Os futuros visitantes podem ficar felizes com isso.
— Top14

Essa é uma pergunta justa, respeitosamente colocada. Obrigado. Eu incluí a solução; você acha que o nome do autor também deve ser incluído? (É público, mas eu me pergunto sobre a etiqueta de qualquer opção.)
— SexxLuthor

Eu acho que é bom como é. Você não afirma ser o "proprietário" desse trecho e não vejo que o autor original desse trecho exige que toda cópia precise de uma atribuição. (Apenas meus dois centavos).
— topskip

35
Não deve haver campo "PasswordSalt" no seu banco de dados, porque você deve usar o bcrypt como seu algoritmo de hash, que cria automaticamente um salt e o inclui no hash retornado. Use também uma função de comparação de tempo constante.
— precisa saber é o seguinte

4
+1 para bcrypt. Além disso, as sessões de gorila com suas chaves de 'criptografia' e 'autenticação' permitiriam armazenar informações de sessão com segurança sem o uso de uma tabela de banco de dados.
— crantok


14

Você usaria o middleware para fazer a autenticação.

Você pode tentar go-http-auth para autenticação básica e digest e gomniauth para OAuth2.

Mas como se autenticar realmente depende do seu aplicativo.

A autenticação introduz o estado / contexto em seus http.Handlers e tem havido alguma discussão sobre isso ultimamente.

Soluções bem conhecidas para o problema de contexto são o gorila / context e o google context descrito aqui .

Fiz uma solução mais geral, sem a necessidade de um estado global em go-on / wrap que possa ser usado em conjunto ou sem os outros dois e se integre perfeitamente ao middleware sem contexto.

O wraphttpauth fornece integração de go-http-auth com go-on / wrap.


Há tantas coisas novas para iniciantes. Eu me pergunto que tipo de coisa um iniciante deve começar. go-http-authou gomniauthou ambos?
— Casper

Alguém aqui implementou o OAuth 1.0 em golang? Autenticação baseada em ConsumerKey e Secret?
— user2888996 2/06

Como posso implementar o oAuth 1.0? Usando chave e segredo do consumidor? Por favor ajude. Eu não estou recebendo nenhuma biblioteca para o mesmo.
— user2888996 2/06

9

Respondendo a isso em 2018. Sugiro usar o JWT (JSON Web Token). A resposta que você marcou como resolvida tem uma desvantagem, que é a viagem que fez na frente (usuário) e na parte traseira (servidor / db). O que é pior se o usuário fez uma solicitação frequente que precisa de autenticação, resultará em uma solicitação inchada de / para o servidor e o banco de dados. Para resolver esse problema, use o JWT, que armazena o token no usuário final, que pode ser usado pelo usuário sempre que precisar de acesso / solicitação. Não é necessário ir ao banco de dados e ao processamento do servidor para verificar a validade do token em pouco tempo.



2

Honestamente, existem muitos métodos e técnicas de autenticação que você pode montar no seu aplicativo e isso depende da lógica e dos requisitos dos aplicativos.
Por exemplo, Oauth2, LDAP, autenticação local, etc.
Minha resposta supõe que você esteja procurando autenticação local, o que significa que você gerencia as identidades do usuário em seu aplicativo. O servidor deve expor um conjunto de API externa que permita aos usuários e administradores Gerenciar as contas e como eles querem se identificar com o Servidor para obter uma comunicação confiável. você acabará criando uma tabela de banco de dados contendo as informações do usuário. onde a senha está em hash para fins de segurança Consulte Como armazenar a senha no banco de dados

vamos assumir os requisitos do aplicativo para autenticar usuários com base em um dos seguintes métodos:

  • autenticação básica (nome de usuário, senha):
    esse método de autenticação depende dos conjuntos de credenciais do usuário no cabeçalho de autorização codificado em base64 e definido em rfc7617 , basicamente quando o aplicativo recebe o usuário solicita suas decodificações e autoriza novamente a senha para compará-la no banco de dados hash se corresponder ao usuário autenticado, caso contrário, retorne o código de status 401 ao usuário.

  • autenticação baseada em certificado:
    esse método de autenticação depende de um certificado digital para identificar um usuário e é conhecido como autenticação x509; portanto, quando o aplicativo recebe solicitações de usuário, lê o certificado do cliente e verifica se ele corresponde ao certificado raiz da CA fornecido. para o APP.

  • token do portador:
    esse método de autenticação depende de tokens de acesso de curta duração. O token do portador é uma cadeia criptográfica, geralmente gerada pelo servidor em resposta a uma solicitação de login. portanto, quando o aplicativo recebe as solicitações do usuário, lê a autorização e valida o token para autenticar o usuário.

No entanto, eu recomendaria o go-guardian para a biblioteca de autenticação, o que é feito por meio de um conjunto extensível de métodos de autenticação conhecidos como estratégias. basicamente, o Go-Guardian não monta rotas nem assume nenhum esquema de banco de dados específico, o que maximiza a flexibilidade e permite que decisões sejam tomadas pelo desenvolvedor.

A configuração de um autenticador go-guardian é simples.

Aqui está o exemplo completo dos métodos acima.

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
}

Uso:

  • Obter token:
curl  -k https://127.0.0.1:8080/login -u stackoverflow:stackoverflow
token: 90d64460d14870c08c81352a05dedd3465940a7
  • Autentique com um token:
curl  -k https://127.0.0.1:8080/resource -H "Authorization: Bearer 90d64460d14870c08c81352a05dedd3465940a7"

Resource!!
  • Autentique com uma credencial de usuário:
curl  -k https://127.0.0.1:8080/resource -u stackoverflow:stackoverflow

Resource!!
  • Autentique com um certificado de usuário:
curl --cert client.pem --key client-key.pem --cacert ca.pem https://127.0.0.1:8080/resource

Resource!!

Você pode ativar vários métodos de autenticação ao mesmo tempo. Você geralmente deve usar pelo menos dois métodos


1

Dê uma olhada no Labstack Echo - ele envolve a autenticação de APIs RESTful e aplicativos de front-end no middleware que você pode usar para proteger rotas de API específicas.

A configuração da autenticação básica, por exemplo, é tão simples quanto a criação de um novo sub-agrupador para a /adminrota:

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

Veja todas as opções de autenticação de middleware do Labstack aqui.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.