在建立網站後端時,我們聽到的一個非常重要的術語是 JWT 身份驗證。 JWT 身份驗證是保護 API 安全的最受歡迎方法之一。 JWT 代表 JSON Web Token,它是一個開放標準,定義了一種在各方之間以 JSON 物件的形式傳輸資訊的方式,並且非常安全。在本文中,我們將討論JWT 身份驗證,最重要的是,我們將使用Gin-Gonic 創建一個用於快速高效JWT 身份驗證的整個項目,這將是一個逐步項目教程,將幫助您創建一個非常快速且行業性的項目。用於您的網站或應用程式後端的級身份驗證 API。
目錄:
JWT 代表 JSON Web Token,它是一個開放標準,定義了一種在各方之間作為 JSON 物件傳輸資訊的方式,並且非常安全。
讓我們試著透過一個例子來理解這一點。考慮這樣一種情況:當我們出現在酒店時,我們走到前台,接待員說“嘿,我能為您做什麼?”。我會說「嗨,我的名字是 Shubham,我來這裡參加一個會議,贊助商正在支付我的酒店費用」。接待員說「好吧,太好了!好吧,我需要看幾件事」。通常他們需要查看我的身份證明,以便證明我是誰,一旦他們確認我是正確的人,他們就會給我一把鑰匙。身份驗證的工作方式與此範例非常相似。
透過 JWT 身份驗證,我們向伺服器發出請求,說“嘿!這是我的使用者名稱和密碼或登入令牌”,網站說“好的,讓我檢查一下”。如果我的使用者名稱和密碼正確,那麼就會給我一個令牌。現在,在伺服器的後續請求中,我不再需要包含我的使用者名稱和密碼。我只需攜帶我的令牌併入住酒店(網站),訪問健身房(數據),我可以訪問游泳池(資訊),並且只能訪問我的酒店房間(帳戶),我無權訪問任何其他酒店房間(其他用戶的帳戶)。此令牌僅在我的狀態期間(從入住時間到結帳時間)授權。之後就沒有用了。現在,酒店還將允許未經任何驗證的人至少看到酒店,在酒店周圍的公共區域漫遊,直到您進入酒店內,同樣,作為匿名用戶,您可以與網站的主頁、登陸頁面進行交互等等
這是 JWT 的範例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
這是該專案的結構。確保在您的工作區中也建立類似的資料夾結構。在此結構中,我們有 6 個資料夾:
並在這些資料夾中建立對應的檔案。
第1 步. 讓我們透過建立一個模組來啟動項目,我的模組名稱是“jwt”,我的用戶名是“1shubham7”,因此我將透過輸入以下內容來初始化我的模組:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
這將建立一個 go.mod 檔案。
步驟 2. 建立一個 main.go 文件,讓我們在 main.go 中建立一個 Web 伺服器。為此,請在文件中加入以下程式碼:
go mod init github.com/1shubham7/jwt
用於檢索環境變數的「os」套件。
我們正在使用 gin-gonic 套件來建立一個 Web 伺服器。
稍後我們將建立一個路由包。
AuthRoutes()、UserRoutes()是routes套件中檔案內的函數,我們稍後會建立它。
第3步.下載gin-gonic套件:
package main import( "github.com/gin-gonic/gin" "os" routes "github.com/1shubham7/jwt/routes" "github.com/joho/godotenv" "log" ) func main() { err := godotenv.Load(".env") if err != nil { log.Fatal("Error locading the .env file") } port := os.Getenv("PORT") if port == "" { port = "1111" } router := gin.New() router.Use(gin.Logger()) routes.AuthRoutes(router) routes.UserRoutes(router) // creating two APIs router.GET("/api-1", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-1"}) }) router.GET("api-2", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-2"}) }) router.Run(":" + port) }
步驟 4. 建立一個 models 資料夾並在其中建立一個 userModel.go 檔案。在 userModel.go 中輸入以下程式碼:
go get github.com/gin-gonic/gin
我們建立了一個名為 User 的結構,並在這個結構中加入了必要的欄位。
json:"first_name" validate:"required, min=2, max=100" 這稱為字段標籤,這些在將 go 代碼解碼和編碼為 JSON 和 JSON to go 期間使用。
這裡 validate:"required, min=2, max=100" 用於驗證特定字段必須至少有 2 個字符,最多 100 個字符。
第5步.建立一個資料庫資料夾,並在其中建立一個databaseConnection.go文件,在其中輸入以下程式碼:
package models import ( "go.mongodb.org/mongo-driver/bson/primitive" "time" ) type User struct { ID primitive.ObjectID `bson:"id"` First_name *string `json:"first_name" validate:"required, min=2, max=100"` Last_name *string `json:"last_name" validate:"required, min=2, max=100"` Password *string `json:"password" validate:"required, min=6"` Email *string `json:"email" validate:"email, required"` //validate email means it should have an @ Phone *string `json:"phone" validate:"required"` Token *string `json:"token"` User_type *string `json:"user_type" validate:"required, eq=ADMIN|eq=USER"` Refresh_token *string `json:"refresh_token"` Created_at time.Time `json:"created_at"` Updated_at time.Time `json:"updated_at"` User_id string `json:"user_id"` }
也要確保下載「mongo」套件:
package database import ( "fmt" "log" "os" "time" "context" "github.com/joho/godotenv" "go.mongodb.org/mongo-driver/mongo" "go/mongodb.org/mongo-driver/mongo/options" ) func DBinstance() *mongo.Client{ err := godotenv.Load(".env") if err != nil { log.Fatal("Error locading the .env file") } MongoDb := os.Getenv("THE_MONGODB_URL") client, err := mongo.NewClient(options.Client().ApplyURI(MongoDb)) if err != nil { log.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err = client.Connect(ctx) if err!=nil{ log.Fatal(err) } fmt.Println("Connected to MongoDB!!") return client } var Client *mongo.Client = DBinstance() func OpenCollection(client *mongo.Client, collectionName string) *mongo.Collection { var collection *mongo.Collection = client.Database("cluster0").Collection(collectionName) return collection }
這裡我們將您的 mongo 資料庫與應用程式連接。
我們使用「godotenv」來載入環境變量,我們將在主目錄的 .env 檔案中設定這些變數。
DBinstance 函數,我們從 .env 檔案中取得「THE_MONGODB_URL」的值(我們將在接下來的步驟中建立該值)並使用該值建立新的 mongoDB 用戶端。
'context' 用於設定 10 秒的逾時時間。
OpenCollection Function() 將 client 和 collectionName 當作輸入並為其建立一個集合。
第 6 步。 對於路由,我們將建立兩個不同的檔案:authRouter 和 userRouter。 authRouter 包括 '/signup' 和 '/login' 。這些將向所有人公開,以便他們可以授權自己。 userRouter 不會向所有人公開。它將包括“/users”和“/users/:user_id”。
建立一個名為routes的資料夾並在其中新增兩個檔案:
在 userRouter.go 中輸入以下程式碼:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
步驟 7. 在 authRouter.go 中輸入以下程式碼:
go mod init github.com/1shubham7/jwt
步驟8.建立一個名為controllers的資料夾並向其中新增一個名為「userController.go」的檔案。在其中輸入以下代碼。
package main import( "github.com/gin-gonic/gin" "os" routes "github.com/1shubham7/jwt/routes" "github.com/joho/godotenv" "log" ) func main() { err := godotenv.Load(".env") if err != nil { log.Fatal("Error locading the .env file") } port := os.Getenv("PORT") if port == "" { port = "1111" } router := gin.New() router.Use(gin.Logger()) routes.AuthRoutes(router) routes.UserRoutes(router) // creating two APIs router.GET("/api-1", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-1"}) }) router.GET("api-2", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-2"}) }) router.Run(":" + port) }
這兩個變數已經在我們之前的程式碼中使用過。
第 10 步。 讓我們先建立 GetUserById() 函數。在 GetUserById() 函數中輸入以下程式碼:
go get github.com/gin-gonic/gin
第 11 步。 讓我們建立一個名為 helpers 的資料夾,並在其中新增一個名為 authHelper.go 的檔案。在 authHelper.go 中輸入以下代碼:
package models import ( "go.mongodb.org/mongo-driver/bson/primitive" "time" ) type User struct { ID primitive.ObjectID `bson:"id"` First_name *string `json:"first_name" validate:"required, min=2, max=100"` Last_name *string `json:"last_name" validate:"required, min=2, max=100"` Password *string `json:"password" validate:"required, min=6"` Email *string `json:"email" validate:"email, required"` //validate email means it should have an @ Phone *string `json:"phone" validate:"required"` Token *string `json:"token"` User_type *string `json:"user_type" validate:"required, eq=ADMIN|eq=USER"` Refresh_token *string `json:"refresh_token"` Created_at time.Time `json:"created_at"` Updated_at time.Time `json:"updated_at"` User_id string `json:"user_id"` }
MatchUserTypeToUserId() 函數僅符合使用者是管理員還是使用者。
我們在 MatchUserTypeToUserId() 中使用 CheckUserType() 函數,這只是檢查一切是否正常(如果我們從 user 獲得的 user_type 與 userType 變數相同。
第 12 步。 我們現在可以處理 userController.go 的 SignUp() 函數:
package database import ( "fmt" "log" "os" "time" "context" "github.com/joho/godotenv" "go.mongodb.org/mongo-driver/mongo" "go/mongodb.org/mongo-driver/mongo/options" ) func DBinstance() *mongo.Client{ err := godotenv.Load(".env") if err != nil { log.Fatal("Error locading the .env file") } MongoDb := os.Getenv("THE_MONGODB_URL") client, err := mongo.NewClient(options.Client().ApplyURI(MongoDb)) if err != nil { log.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err = client.Connect(ctx) if err!=nil{ log.Fatal(err) } fmt.Println("Connected to MongoDB!!") return client } var Client *mongo.Client = DBinstance() func OpenCollection(client *mongo.Client, collectionName string) *mongo.Collection { var collection *mongo.Collection = client.Database("cluster0").Collection(collectionName) return collection }
我們建立了一個 User 類型的變數使用者。
validationErr 用來驗證結構體標籤,我們已經討論過這一點。
我們也使用計數變數來驗證。就像如果我們已經找到包含用戶電子郵件的文檔,那麼計數將大於 0,然後我們可以處理該錯誤(錯誤)
然後我們使用 'time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))' 來設定 Created_at、Updated_at 結構體欄位的時間。當使用者嘗試註冊時,函數 SignUp() 將運行,並且該特定時間將儲存在 Created_at、Updated_at 結構體欄位中。
然後我們使用GenerateAllTokens()函數建立令牌,我們將在下一步中在同一套件的tokenHelper.go檔案中建立令牌。
我們還有一個 HashPassword() 函數,它除了對 user.password 進行哈希處理並用哈希後的密碼替換 user.password 之外什麼也不做。我們稍後也會創建那個東西。
然後我們只需將資料和令牌等插入到 userCollection
如果一切順利,我們將返回 StatusOK。
步驟 13. 在 helpers 資料夾中建立一個名為「tokenHelper.go」的文件,並在其中輸入以下程式碼。
go get go.mongodb.org/mongo-driver/mongo
也要確保下載 github.com/dgrijalva/jwt-go 套件:
package routes import ( "github.com/gin-gonic/gin" controllers "github.com/1shubham7/jwt/controllers" middleware "github.com/1shubham7/jwt/middleware" ) // user should not be able to use userRoute without the token func UserRoutes (incomingRoutes *gin.Engine) { incomingRoutes.Use(middleware.Authenticate()) // user routes are public routes but these must be authenticated, that // is why we have Authenticate() before these incomingRoutes.GET("/users", controllers.GetUsers()) incomingRoutes.GET("users/:user_id", controllers.GetUserById()) }
這裡我們使用 github.com/dgrijalva/jwt-go 來產生令牌。
我們正在建立一個名為 SignedDetails 的結構,其中包含產生令牌所需的欄位名稱。
我們正在使用 NewWithClaims 產生新令牌,並將值賦予 Claims 和 refreshClaims 結構。 Claims 在使用者首次使用時擁有令牌,refreshClaims 在使用者必須刷新令牌時擁有令牌。也就是說,他們之前有一個令牌,但現在已過期。
time.Now().Local().Add(time.Hour *time.Duration(120)).Unix() 用來設定令牌的到期時間。
然後我們只回傳三個東西 - token、refreshToken 和 err,我們在 SignUp() 函數中使用它們。
步驟 14. 在與 SignUp() 函數相同的檔案中,讓我們建立我們在步驟 9 中討論過的 HashPassword() 函數。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
我們只是使用 bcrypt 套件中的GenerateFromPassword()方法,該方法用於根據實際密碼產生雜湊密碼。
這很重要,因為我們不希望駭客入侵我們的系統並竊取所有密碼,這也是為了用戶的隱私。
[]byte(位元組數組)只是字串。
第 15 步。 讓我們在 'userController.go' 中建立 Login() 函數,在後面的步驟中我們可以建立 Login() 使用的函數:
go mod init github.com/1shubham7/jwt
我們正在建立兩個 User 類型的變量,它們是 user 和 Founduser。並將請求中的資料提供給使用者。
在'userCollection.FindOne(ctx, bson.M{"email": user.Email}).Decode(&foundUser)' 的幫助下,我們透過他/她的電子郵件查找用戶,如果找到,將其儲存在foundUser變數中。
然後我們使用VerifyPassword()函數來驗證密碼並儲存它,記住我們在VerifyPassword()中將指標作為參數,如果沒有,它將建立參數中的新實例而不是參數實際上改變了它們。
我們將在下一個步驟中建立VerifyPassword()。
然後我們簡單地使用GenerateAllTokens()和UpdateAllTokens()來產生和更新令牌和refreshToken(它們基本上是令牌)。
每一步,我們都在處理錯誤。
第 16 步。 讓我們在與 Login() 函數相同的檔案中建立VerifyPassword() 函數:
package main import( "github.com/gin-gonic/gin" "os" routes "github.com/1shubham7/jwt/routes" "github.com/joho/godotenv" "log" ) func main() { err := godotenv.Load(".env") if err != nil { log.Fatal("Error locading the .env file") } port := os.Getenv("PORT") if port == "" { port = "1111" } router := gin.New() router.Use(gin.Logger()) routes.AuthRoutes(router) routes.UserRoutes(router) // creating two APIs router.GET("/api-1", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-1"}) }) router.GET("api-2", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-2"}) }) router.Run(":" + port) }
我們只是使用 bcrypt 套件中的 CompareHashAndPassword() 方法,該方法用於比較雜湊密碼。並根據結果傳回一個布林值。
[]byte(位元組數組)只是字串,但 []byte 有助於比較。
第 17 步。 讓我們在「tokenHelper.go」檔案中建立 UpdateAllTokens() 函數:
go get github.com/gin-gonic/gin
我們正在建立一個名為 updateObj 的變量,其類型為 Primitive.D。 MongoDB 的 Go 驅動程式中的 Primitive.D 類型是 BSON 文件的表示。
Append() 每次執行時都會向 updateObj 追加一個鍵值對。
然後使用'time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))' 將當前時間(更新發生且函數運行的時間)更新為Updated_at .
程式碼區塊的其餘部分正在使用 mongoDB 集合的 UpdateOne 方法執行更新。
最後一步我們也會處理錯誤,以防出現任何錯誤。
第 18 步。 在繼續之前,讓我們下載 go.mongodb.org/mongo-driver 套件:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第 19 步。 現在讓我們研究 GetUserById() 函數。請記住,GetUserById() 是供使用者存取自己的資訊的,管理員可以存取所有使用者數據,使用者只能存取自己的資料。
go mod init github.com/1shubham7/jwt
從請求中取得 user_id 並將其儲存在 userId 變數中。
建立一個名為 user 的變量,其類型為 User。然後只需在 user_id 的幫助下在我們的資料庫中搜尋用戶,如果 user_id 匹配,我們將將該人的資訊儲存在 user 變數中。
如果一切順利,StatusOk
我們也在處理每一步的錯誤。
第 20 步。 現在讓我們來研究 GetUsers() 函數。請記住,只有管理員可以存取 GetUsers() 函數,因為它將包含所有使用者的資料。在與 GetUserById()、Login() 和 SignUp() 函數相同的檔案中建立 GetUsers() 函數:
package main import( "github.com/gin-gonic/gin" "os" routes "github.com/1shubham7/jwt/routes" "github.com/joho/godotenv" "log" ) func main() { err := godotenv.Load(".env") if err != nil { log.Fatal("Error locading the .env file") } port := os.Getenv("PORT") if port == "" { port = "1111" } router := gin.New() router.Use(gin.Logger()) routes.AuthRoutes(router) routes.UserRoutes(router) // creating two APIs router.GET("/api-1", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-1"}) }) router.GET("api-2", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-2"}) }) router.Run(":" + port) }
首先,我們將檢查請求是否來自管理員,我們藉助先前步驟中建立的 CheckUserType() 來完成此操作。
然後我們正在設定您想要每頁的記錄數。
我們可以透過從請求中取得 recodePerPage 並將其轉換為 int 來做到這一點,這是由 srtconv 完成的。
如果recordPerPage設定錯誤或recordPerPage小於1,預設每頁有9筆記錄
同樣,我們在變數「page」中取得頁碼。
預設情況下,我們的頁碼為 1 和 9 recordPerPage。
然後我們建立了三個階段(matchStage、groupStage、projectStage)。
然後我們使用 Aggregate() 函數在 Mongo 管道中設定這三個階段
此外,我們每一步都會處理錯誤。
第 21 步。 我們的 'userController.go' 現已準備就緒,這是完成後 'userController.go' 檔案的樣子:
go get github.com/gin-gonic/gin
第 22 步。 現在我們可以處理身分驗證部分了。為此,我們將建立一個身份驗證中間件。建立一個名為「middleware」的資料夾,並在其中建立一個名為「authMiddleware.go」的檔案。在文件中輸入以下代碼:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
步驟 23. 現在讓我們建立 ValidateToken() 函數,我們將在 'tokenHelper.go' 中建立此函數:
go mod init github.com/1shubham7/jwt
ValidateToken 接受signedToken 並傳回該Token 的SignedDetails 以及錯誤訊息。 "" 如果沒有錯誤的話。
ParseWithClaims() 函數用於取得令牌並將其儲存在名為 token 的變數中。
然後我們使用令牌上的 Claims 方法檢查令牌是否正確。我們將結果儲存在宣告變數中。
然後我們使用 ExpiresAt() 函數檢查令牌是否已過期,如果當前時間大於 ExpiresAt 時間,則令牌已過期。
然後簡單地傳回宣告變數以及訊息。
第 24 步。 現在我們基本上已經完成了,讓我們執行“go mod tidy”,此命令會檢查您的go.mod 文件,它會刪除我們安裝但未使用的所有套件/依賴項並下載我們正在使用但尚未下載的所有相依性。
package main import( "github.com/gin-gonic/gin" "os" routes "github.com/1shubham7/jwt/routes" "github.com/joho/godotenv" "log" ) func main() { err := godotenv.Load(".env") if err != nil { log.Fatal("Error locading the .env file") } port := os.Getenv("PORT") if port == "" { port = "1111" } router := gin.New() router.Use(gin.Logger()) routes.AuthRoutes(router) routes.UserRoutes(router) // creating two APIs router.GET("/api-1", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-1"}) }) router.GET("api-2", func(c *gin.Context){ c.JSON(200, gin.H{"success":"Access granted for api-2"}) }) router.Run(":" + port) }
我們的 JWT 驗證專案已準備就緒,要最終運行應用程序,請在終端機中輸入以下命令:
go get github.com/gin-gonic/gin
您將會得到類似的輸出:
這將使伺服器啟動並運行,您可以使用curl或Postman API來發送請求和接收回應。或者您可以簡單地將此 API 與前端框架整合。至此,我們的身分驗證 API 已準備就緒,放心!
在本文中,我們討論了建立 JWT 驗證的最快方法之一,我們在專案中使用了 Gin-Gonic 框架。這不是您的“只是另一個身份驗證 API”。 Gin 比 NodeJS 快 300%,這使得這個身份驗證 API 非常快速且有效率。我們使用的專案結構也是業界級的專案結構。您可以進行進一步的更改,例如將 SECRET_KEY 儲存在 .env 檔案中等等,以使此 API 變得更好。您也可以在這裡找到該專案的原始碼 - 1Shubham7/go-jwt-token.
確保遵循所有步驟來創建專案並添加更多功能,並使用程式碼來更好地理解它。學習身份驗證的最佳方法是建立自己的專案。
以上是在 Go (Golang) 中實現 JWT 身份驗證的逐步指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!