사람들은 Go에서 인증을 어떻게 관리하고 있습니까? [닫은]


187

Go에서 RESTful API 및 JS 프론트 엔드 앱을 구축하는 경우 인증을 어떻게 관리하고 있습니까? 특정 라이브러리 나 기술을 사용하고 있습니까?

이것에 대해 너무 작은 토론을 발견 한 것에 놀랐습니다. 나는 다음과 같은 대답을 명심하고 내 구현을 개발하지 않도록 노력하고 있습니다.

ASP.Net의 인증 양식

모두 각자의 솔루션을 별도로 코딩하고 있습니까?


5
인증은 사용중인 응용 프로그램 유형에 따라 크게 다릅니다. 모든 규모에 맞는 솔루션은 없습니다. 또한 해결하기 어려운 문제입니다. 결정적인 문서를 찾지 못한 것 같습니다.
jimt

21
빠른 답변 감사합니다. 이해했지만 대부분의 언어와 프레임 워크는 대부분의 앱이 공유하는 가장 일반적인 인증 요구 사항을 다루고 광범위한 커뮤니티 참여 및 지원을 제공하는 인증 솔루션을 제공합니다. 어려운 문제라는 데 동의합니다. 협력적인 노력으로 가장 큰 혜택을 얻지 않습니까? (이것은 오픈 소스이기 때문에 불만이 아닙니다. 그러나 우리 모두가 바퀴를 재발 명하고 있다는 관찰이 더
많습니다

13
@jimt 어려운 문제라는 사실은 필사자들에게 잘못 될 수없는 코 노니 컬 솔루션을 제공하는 것이 더욱 중요합니다.
tymtam

이 질문은 여론 조사 주제이므로 주제를 벗어난 주제로 마무리하려고합니다.
Flimzy

답변:


115

이 질문은 많은 견해를 얻었고 인기있는 질문 배지가 있으므로이 주제에 많은 관심을 가지고 있으며 많은 사람들이 정확히 똑같은 것을 묻고 인터 웹에서 답을 찾지 않습니다.

이용 가능한 대부분의 정보는 텍스트를 손으로 흔드는 것과 동일하며 "독자 운동"으로 남습니다. ;)

그러나 마지막으로 golang-nuts 메일 링리스트의 회원이 제공 한 구체적인 예를 찾았습니다.

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

이는 사용자 정의 인증의 기초로 제안 된 스키마 및 서버 측 구현을 제공합니다. 클라이언트 측 코드는 여전히 귀하의 몫입니다.

(기사의 저자가 이것을 보길 바랍니다 : 감사합니다!)

발췌 (및 재 포맷) :


"저는 다음과 같은 디자인을 제안합니다.

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
)
  • 사용자가 TLS에서 POST를 통해 사이트에 로그인 할 때 암호가 유효한지 확인하십시오.
  • 그런 다음 임의의 세션 키 (예 : 50 개 이상의 암호화 랜드 문자 및 물건)를 안전한 쿠키에 발행하십시오.
  • 해당 세션 키를 UserSession 테이블에 추가하십시오.
  • 그런 다음 해당 사용자를 다시 볼 때 먼저 UserSession 테이블을 눌러 SessionKey가 유효한 LoginTime과 함께 있고 LastSeenTime과 User가 삭제되지 않았는지 확인하십시오. UserSession에서 타이머가 자동으로 오래된 행을 지우도록 디자인 할 수 있습니다. "

8
우리는 여기에 자체 포함 된 사이트를 좋아하는 경향이 있으므로 여기에 솔루션을 게시 하시겠습니까? 링크가 적시에 변경 될 경우를 대비하여 (링크 썩음 등) 미래의 방문자는 이에 대해 기뻐할 것입니다.
topskip

그것은 정중 한 질문입니다. 감사합니다. 나는 해결책을 포함시켰다. 저자의 이름도 포함되어야한다고 생각하십니까? (공개이지만 두 옵션 중 하나의 예절에 대해 궁금합니다.)
SexxLuthor

나는 그것이 좋은 것이라고 생각합니다. 이 스 니펫의 "소유자"라고 주장하지 않으며이 스 니펫의 원래 작성자가 모든 사본에 속성이 필요하다는 것을 알 수 없습니다. (나의 2 센트 만).
topskip

35
bcrypt를 해시 알고리즘으로 사용해야하므로 데이터베이스에 "PasswordSalt"필드가 없어야합니다.이 알고리즘은 솔트를 자동으로 생성하여 반환 된 해시에 포함합니다. 일정 시간 비교 기능도 사용하십시오.
0xdabbad00

4
bcrypt의 경우 +1 또한 '암호화'및 '인증'키가있는 고릴라 세션을 사용하면 DB 테이블을 사용하지 않고도 세션 정보를 안전하게 저장할 수 있습니다.
crantok


14

미들웨어를 사용하여 인증을 수행합니다.

기본 및 요약 인증 및 gomniauth에 대해 go-http-auth 를 시도 할 수 있습니다. 하고 OAuth2를 위해 를 .

그러나 인증 방법은 앱에 따라 다릅니다.

인증은 http.Handlers에 상태 / 컨텍스트를 소개하며 최근에 이에 대한 논의가있었습니다.

컨텍스트 문제에 대한 잘 알려진 해결책 은 여기에 설명 된 고릴라 / 컨텍스트Google 컨텍스트입니다 . 입니다.

go-on / wrap 에서 전역 상태가 필요없는보다 일반적인 솔루션을 만들었습니다. 함께 사용하거나 다른 두 가지없이 사용할 수 있으며 컨텍스트 프리 미들웨어와 잘 통합됩니다.

wraphttpauth 는 go-on / wrap과 go-http-auth의 통합을 제공합니다.


초보자에게는 많은 새로운 것들이 있습니다. 초보자가 시작해야 할 것이 무엇인지 궁금합니다. go-http-auth또는 gomniauth둘 다?
캐스퍼

여기 누구든지 golang에서 OAuth 1.0을 구현 했습니까? ConsumerKey 및 Secret 기반 인증?
user2888996

oAuth 1.0을 어떻게 구현할 수 있습니까? 소비자 키와 비밀을 사용하십니까? 도와주세요. 같은 라이브러리를 얻지 못했습니다.
user2888996

9

JWT (JSON Web Token)를 사용하는 것이 좋습니다. 해결 된 것으로 표시된 답변에는 단점이 있습니다. 이것은 front (user)와 back (server / db)을 한 여행입니다. 사용자가 인증을 필요로하는 빈번한 요청을 수행하면 서버 및 데이터베이스에 대한 요청이 부풀려집니다. 이 문제를 해결하기 위해 액세스 / 요청이 필요할 때마다 사용자가 사용할 수있는 토큰을 사용자쪽에 저장하는 JWT를 사용하십시오. 토큰 유효성을 확인하기 위해 데이터베이스 및 서버 처리로 이동할 필요가 없습니다.



2

솔직히 응용 프로그램에 탑재 할 수 있고 응용 프로그램 비즈니스 논리 및 요구 사항에 따라 많은 인증 방법과 기술이 있습니다.
예를 들어 Oauth2, LDAP, 로컬 인증 등이 있습니다.
제 답변은 로컬 인증을 찾고 있다고 가정합니다. 즉, 응용 프로그램에서 사용자 ID를 관리해야합니다. 서버는 사용자 및 관리자가 계정을 관리하고 신뢰할 수있는 통신을 달성하기 위해 서버에 자신을 식별하는 방법을 허용하는 외부 API 세트를 공개해야합니다. 결국 사용자 정보를 보유한 DB 테이블을 생성하게됩니다. 보안 목적으로 비밀번호가 해시되는 위치 데이터베이스에 비밀번호를 저장하는 방법을

다음 방법 중 하나를 기반으로 사용자를 인증하기 위해 앱 요구 사항을 가정합니다.

  • 기본 인증 (사용자 이름, 비밀번호) :
    이 인증 방법은 base64로 인코딩되고 rfc7617에 정의 된 권한 부여 헤더의 사용자 신임 정보 세트에 따라 다릅니다. 기본적으로 앱이 사용자에게 요청을 받으면 승인을 해독하고 비밀번호를 다시 해시하여 DB 내에서 비교합니다. 인증 된 사용자와 일치하는 경우 해시 그렇지 않으면 401 상태 코드를 사용자에게 반환합니다.

  • 인증서 기반 인증 :
    이 인증 방법은 사용자를 식별하기 위해 디지털 인증서에 의존하며이를 x509 인증이라고합니다. 따라서 앱이 사용자 요청을 받으면 클라이언트의 인증서를 읽고 제공된 CA 루트 인증서와 일치하는지 확인합니다. APP에.

  • 베어러 토큰 :
    이 인증 방법은 수명이 짧은 액세스 토큰에 따라 다릅니다. 베어러 토큰은 일반적으로 로그인 요청에 대한 응답으로 서버에서 생성되는 암호 문자열입니다. 따라서 앱이 사용자 요청을 받으면 권한을 읽고 토큰을 확인하여 사용자를 인증합니다.

그러나 전략이라고 알려진 확장 가능한 인증 방법을 통해 인증 라이브러리에 go-guardian 을 추천 합니다. 기본적으로 Go-Guardian은 경로를 마운트하거나 특정 데이터베이스 스키마를 가정하지 않으므로 유연성을 최대화하고 개발자가 의사 결정을 내릴 수 있습니다.

보호자 인증자를 설정하는 것은 간단합니다.

위의 방법의 전체 예는 다음과 같습니다.

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
}

용법:

  • 토큰 획득 :
curl  -k https://127.0.0.1:8080/login -u stackoverflow:stackoverflow
token: 90d64460d14870c08c81352a05dedd3465940a7
  • 토큰으로 인증하십시오.
curl  -k https://127.0.0.1:8080/resource -H "Authorization: Bearer 90d64460d14870c08c81352a05dedd3465940a7"

Resource!!
  • 사용자 신임 정보로 인증하십시오.
curl  -k https://127.0.0.1:8080/resource -u stackoverflow:stackoverflow

Resource!!
  • 사용자 인증서로 인증하십시오.
curl --cert client.pem --key client-key.pem --cacert ca.pem https://127.0.0.1:8080/resource

Resource!!

한 번에 여러 인증 방법을 활성화 할 수 있습니다. 일반적으로 두 가지 이상의 방법을 사용해야합니다


1

Labstack Echo를 살펴보십시오. RESTful API 및 프론트 엔드 애플리케이션의 인증을 특정 API 라우트를 보호하는 데 사용할 수있는 미들웨어로 랩핑합니다.

예를 들어, 기본 인증을 설정하는 것은 /admin라우트에 대한 새 서브 라우터를 작성하는 것만 큼 간단 합니다.

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

여기에서 Labstack의 미들웨어 인증 옵션을 모두 참조하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.