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?

SexxLuthor
fonte
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. "
SexxLuthor
fonte
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.

metakeule
fonte
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.

mfathirirhas
fonte
6

Outro pacote de código aberto para lidar com a autenticação com cookies é o enablepauth .

(escrito por mim, a propósito)

Cameron Little
fonte
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

shaj13
fonte
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.

Adil B
fonte