在创建网站后端时,我们听到的一个非常重要的术语是 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中文网其他相关文章!