GoでRESTful APIとJSフロントエンドアプリを構築している人のために、認証をどのように管理していますか?特定のライブラリやテクニックを使用していますか?
これについての議論がほとんどないことに驚いています。私は次のような答えを心に留めており、独自の実装を開発しないようにしています:
誰もが独自に独自のソリューションをコーディングしていますか?
GoでRESTful APIとJSフロントエンドアプリを構築している人のために、認証をどのように管理していますか?特定のライブラリやテクニックを使用していますか?
これについての議論がほとんどないことに驚いています。私は次のような答えを心に留めており、独自の実装を開発しないようにしています:
誰もが独自に独自のソリューションをコーディングしていますか?
回答:
この質問には多くの見解があり、人気の質問バッジが付いているので、このトピックには多くの潜在的な関心があり、多くの人がまったく同じことを求めてインターウェブで答えを見つけていません。
入手可能な情報のほとんどは、「読者のための演習」として残された、手で波打ったものに相当するテキストになります。;)
しかし、ついに私は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
)
ミドルウェアを使用して認証を行います。
基本認証とダイジェスト認証とgomniauthにgo-http-authを試すことができますはgomniauthます。
ただし、認証方法は実際にはアプリによって異なります。
認証は、http /ハンドラーに状態/コンテキストを導入しますが、最近それについていくつかの議論がありました。
コンテキストの問題によく知られている解決策はあるゴリラ/コンテキストおよびコンテキストをGoogle説明ここに。
go-on / wrapでグローバル状態を必要とせずに一緒に、または他の2つを使用せずにコンテキストフリーミドルウェアとうまく統合する、より一般的なソリューションを作成しました。
wraphttpauthは、go-http-authとgo-on / wrapの統合を提供します。
go-http-auth
またはgomniauth
それらの両方?
2018年にこれに答えます。JWT(JSON Web Token)を使用することをお勧めします。あなたが解決とマークした答えには欠点があります。それは、それがフロント(ユーザー)とバック(サーバー/ db)を行った旅行です。ユーザーが認証を必要とするリクエストを頻繁に行った場合、さらに悪いのは、サーバーとデータベースとの間の肥大したリクエストになります。これを解決するには、アクセス/リクエストが必要なときにいつでもユーザーが使用できるトークンをユーザーエンドに保存するJWTを使用します。トークンの有効性を確認するためにデータベースとサーバーの処理に移動する必要はありません。
正直なところ、アプリケーションに実装できる認証方法や技術は数多くあり、アプリケーションのビジネスロジックや要件によって異なります。
たとえば、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つの方法を使用する必要があります
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
}))