Goで認証をどのように管理していますか?[閉まっている]


187

GoでRESTful APIとJSフロントエンドアプリを構築している人のために、認証をどのように管理していますか?特定のライブラリやテクニックを使用していますか?

これについての議論がほとんどないことに驚いています。私は次のような答えを心に留めており、独自の実装を開発しないようにしています:

ASP.Netの認証フォーム

誰もが独自に独自のソリューションをコーディングしていますか?


5
認証は、使用するアプリケーションのタイプに大きく依存します。万能のソリューションはありません。さらに、それは解決するのが難しい問題です。これが、決定的なドキュメントが見つからない理由です。
jimt 2014

21
こんにちは、迅速な対応に感謝します。理解されていますが、ほとんどの言語とフレームワークは、大多数のアプリで共有される最も一般的な認証要件をカバーする認証ソリューションを考案し、幅広いコミュニティの参加とサポートを提供しています。私はそれが難しい問題であることに同意します。それらは協力的な努力から最も利益を得ませんか?(これはオープンソースであるため、これは不満ではありませんが、私たち全員が車輪を再発明しているという観察の詳細です。)
SexxLuthor 14

12
@jimtそれが難しい問題であるという事実は、私たちが人間に間違ってはいけない円錐形のソリューションを提供することをさらに重要にします。
ティムタム

この質問は投票用の質問であるため、トピック外として閉じることに投票します。
Flimzy

回答:


115

この質問には多くの見解があり、人気の質問バッジが付いているので、このトピックには多くの潜在的な関心があり、多くの人がまったく同じことを求めてインターウェブで答えを見つけていません。

入手可能な情報のほとんどは、「読者のための演習」として残された、手で波打ったものに相当するテキストになります。;)

しかし、ついに私はgolang-nutsメーリングリストのメンバーから提供された(寛大に)1つの具体的な例を見つけました。

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経由でサイトにログインするときに、パスワードが有効かどうかを確認します。
  • 次に、ランダムなセッションキーを発行します。たとえば、安全なCookieに50以上の暗号ランド文字などを入れます。
  • そのセッションキーをUserSessionテーブルに追加します。
  • 次に、そのユーザーが再び表示されたら、まずUserSessionテーブルにアクセスして、SessionKeyが有効なLoginTimeとLastSeenTimeでそこにあり、ユーザーが削除されていないかどうかを確認します。タイマーがUserSessionの古い行を自動的にクリアするように設計できます。」

8
ここSOで自己完結型のサイトを好む傾向があるので、ここに解決策を投稿してもよろしいですか?リンクがやがて変更される場合に備えて(リンクの腐敗など)将来の訪問者はこれについて喜ぶでしょう。
topskip

これは公平な質問です。ありがとうございました。ソリューションを含めました。著者の名前も含める必要があると思いますか?(それは公開されていますが、どちらのオプションのエチケットについても不思議に思います。)
SexxLuthor

そのままでいいと思います。あなたはこのスニペットの「所有者」であると主張していませんし、私はこのスニペットの元の作者がすべてのコピーが帰属を必要とすることを要求しているとは思えません。(私の2セントのみ)。
topskip

35
ハッシュアルゴリズムとしてbcryptを使用する必要があるため、データベースに「PasswordSalt」フィールドを含めないでください。これにより、ソルトが自動的に作成され、返されるハッシュに含まれます。また、一定時間比較機能を使用してください。
0xdabbad00 2015

4
bcryptの場合は+1。また、「暗号化」および「認証」キーを備えたゴリラセッションでは、DBテーブルを使用せずにセッション情報を安全に保存できます。
crantok 2016年


14

ミドルウェアを使用して認証を行います。

基本認証とダイジェスト認証とgomniauthにgo-http-authを試すことができますはgomniauthます。

ただし、認証方法は実際にはアプリによって異なります。

認証は、http /ハンドラーに状態/コンテキストを導入しますが、最近それについていくつかの議論がありました。

コンテキストの問題によく知られている解決策はあるゴリラ/コンテキストおよびコンテキストをGoogle説明ここに

go-on / wrapでグローバル状態を必要とせずに一緒に、または他の2つを使用せずにコンテキストフリーミドルウェアとうまく統合する、より一般的なソリューションを作成しました。

wraphttpauthは、go-http-authとgo-on / wrapの統合を提供します。


初心者には新しいものがたくさんあります。初心者はどんなことから始めたらいいのかしら。go-http-authまたはgomniauthそれらの両方?
キャスパー

ここの誰かがOAuth 1.0をgolangで実装しましたか?ConsumerKeyおよび秘密ベースの認証?
user2888996

どうすればoAuth 1.0を実装できますか?コンシューマキーとシークレットを使用していますか?助けてください。同じライブラリは入手できません。
user2888996

10

2018年にこれに答えます。JWT(JSON Web Token)を使用することをお勧めします。あなたが解決とマークした答えには欠点があります。それは、それがフロント(ユーザー)とバック(サーバー/ db)を行った旅行です。ユーザーが認証を必要とするリクエストを頻繁に行った場合、さらに悪いのは、サーバーとデータベースとの間の肥大したリクエストになります。これを解決するには、アクセス/リクエストが必要なときにいつでもユーザーが使用できるトークンをユーザーエンドに保存するJWTを使用します。トークンの有効性を確認するためにデータベースとサーバーの処理に移動する必要はありません。



2

正直なところ、アプリケーションに実装できる認証方法や技術は数多くあり、アプリケーションのビジネスロジックや要件によって異なります。
たとえば、Oauth2、LDAP、ローカル認証などです。
私の答えは、ローカル認証を探していると想定しています。つまり、アプリケーションでユーザーのIDを管理します。サーバーは、一連の外部APIを公開する必要があります。これにより、ユーザーと管理者は、アカウントを管理し、信頼できる通信を実現するためにサーバーに対して自分自身をどのように識別するかを管理できます。ユーザーの情報を保持するDBテーブルを作成することになります。パスワードは、セキュリティ上の目的でハッシュされます。データベースにパスワードを保存する方法を参照してください。

次のいずれかの方法に基づいてユーザーを認証するアプリ要件を想定します。

  • 基本認証(ユーザー名、パスワード):
    この認証方法は、base64でエンコードされ、rfc7617で定義されたAuthorizationヘッダーで設定されたユーザー資格情報に依存します。認証されたユーザーと一致する場合はハッシュ、それ以外の場合は401ステータスコードをユーザーに返します。

  • 証明書ベースの認証:
    この認証方法は、ユーザーを識別するデジタル証明書に依存します。これはx509認証と呼ばれるため、アプリがユーザーのリクエストを受信すると、クライアントの証明書を読み取り、提供されているCAルート証明書と一致することを確認しますAPPに。

  • ベアラートークン:
    この認証方法は、存続期間の短いアクセストークンに依存します。ベアラートークンは、通常はログインリクエストへの応答としてサーバーによって生成される暗号文字列です。そのため、アプリはユーザーリクエストを受け取ると、承認を読み取り、トークンを検証してユーザーを認証します。

ただし、 戦略と呼ばれる拡張可能な認証方法のセットを介して行う認証ライブラリには、go-guardianをお勧めします。基本的に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!!

一度に複数の認証方法を有効にできます。通常、少なくとも2つの方法を使用する必要があります


1

Labstack Echoを見てください。RESTfulAPIとフロントエンドアプリケーションの認証を、特定の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.