JSON Web 令牌 (JWT) 是处理在线身份验证的流行方法,您可以使用任何服务器端编程语言实现 JWT 身份验证。
对于一般的 JWT 阅读背景知识,我建议通过 LogRocket 博客上的这些文章了解更多关于JWT、最佳实践和使用 JWT 保护 RESTful API的信息。
本文旨在帮助您开始使用该包在您的 Go Web 应用程序中实现 JWT 身份验证。golang-jwt
由于其特性和易用性,该包是在 Go 中实现 JWT 最流行的包。该包提供了生成和验证 JWT 的功能。golang-jwt``golang-jwt
先决条件
您需要满足这些基本要求才能充分利用本教程。
- 在您的机器上安装 Go 1.16 或更高版本(出于安全原因)
- 使用 Go 或任何其他语言构建 Web 应用程序的经验(可选)
目录
- 开始使用包Golang-JWT
- 在 Go 中设置 Web 服务器
- 使用包生成JWTGolang-JWT
- 验证 JWT 令牌
- 从 JWT 令牌中提取声明
Golang-JWT 包入门
设置好 Go 工作区并初始化 Go 模块文件后,在终端上的工作区目录中运行以下命令以安装包:go.mod``golang-jwt
go get github.com/golang-jwt/jwt
安装后,创建一个 Go 文件并导入这些包和模块。golang-jwt
import (
"log"
"encoding/json"
"github.com/golang-jwt/jwt"
"net/http"
"time"
)
您将在本教程中使用这些包来记录错误、设置服务器和设置令牌过期时间。
在 Go 中设置 Web 服务器
让我们从创建一个简单的 Web 服务器开始,该服务器的端点将由 JWT 保护。
func main() {
http.HandleFunc("/home", handlePage)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Println("There was an error listening on port :8080", err)
}
}
主函数使用handlePage您将设置的处理程序函数设置主端点。该handlePage函数将使用 JWT 保护页面。服务器设置为侦听端口,但您可以使用您选择的任何端口。:8080
如果在请求正文编码后请求被授权,则处理函数将返回结构的编码 JSON 作为对客户端的响应handlePage。Message
type Message struct {
Status string `json:"status"`
Info string `json:"info"`
}
func handlePage(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json")
var message Message
err := json.NewDecoder(request.Body).Decode(&message)
if err != nil {
return
}
err = json.NewEncoder(writer).Encode(message)
if err != nil {
return
}
}
此时,该handlePage功能尚未经过身份验证,向页面发出请求将可以自由工作。您将在本教程的后面部分学习如何向处理程序函数添加身份验证。
使用包生成 JWT 进行身份验证Golang-JWT
您将需要一个密钥来使用该包生成 JWT 令牌。这是本教程的示例私钥;但是,您应该为您的密钥使用加密安全字符串,并从环境变量文件 (.env) 中加载它。golang-jwt
查看这篇文章,了解如何在 Go 应用程序中使用环境变量。
var sampleSecretKey = []byte("SecretYouShouldHide")
请注意,任何拥有您用于 JWT 的密钥的人都可以对您的应用程序的用户进行身份验证。在这种sampleSecretKey情况下,变量保存私钥。
超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →
这是一个生成 JWT 令牌的函数。该函数应返回一个字符串和一个错误。如果生成 JWT 时出错,该函数将返回一个空字符串和错误。如果没有错误,该函数将返回 JWT 字符串和nil类型。
func generateJWT() (string, error) {
}
New您可以使用JWT 包的方法创建新令牌。该New方法采用签名方法(JWT 的加密算法)并返回 JWT 令牌。
token := jwt.New(jwt.SigningMethodEdDSA)
如果要修改JWT,可以使用Claimstoken的方法。
claims := token.Claims.(jwt.MapClaims)
claims["exp"] = time.Now().Add(10 * time.Minute)
claims["authorized"] = true
claims["user"] = "username"
在这种情况下,您将使用time模块以及用户名和授权状态为 JWT 设置过期时间,即十分钟。在尝试验证 JWT 时,您将能够检索声明。
生成 JWT 的最后一部分是使用您的密钥对字符串进行签名。您可以使用令牌的SignedString方法对令牌字符串进行签名。该SignedString方法采用密钥并返回签名的令牌字符串。
tokenString, err := token.SignedString(sampleSecretKey)
if err != nil {
return "", err
}
return tokenString, nil
如果签署令牌时出现错误,您可以返回空字符串和错误。 不像cookies,你不需要存储JWT;您所需要的只是您的签名密钥来验证令牌。
验证 JWT 令牌
验证 JWT 的传统方法使用中间件(处理函数接受其他处理函数进行操作)。下面介绍如何使用中间件来验证请求是否被授权。
来自 LogRocket 的更多精彩文章:
- 不要错过来自 LogRocket 的精选时事通讯The Replay
- 了解LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题
- 使用 React 的 useEffect优化应用程序的性能
- 在多个 Node 版本之间切换
- 了解如何使用 AnimXYZ 为您的 React 应用程序制作动画
- 探索 Tauri,一个用于构建二进制文件的新框架
- 比较NestJS 与 Express.js
func verifyJWT(endpointHandler func(writer http.ResponseWriter, request *http.Request)) http.HandlerFunc {
}
该verifyJWT函数是一个中间件,它接收您要验证的请求的处理函数。处理函数使用请求标头中的令牌参数来验证请求并根据状态进行响应。
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
})
如果请求被授权,该verifyJWT函数返回作为参数传入的处理函数。
验证 JWT 的第一步是检查请求标头中的令牌。
if request.Header["Token"] != nil {
}
如果有令牌,您可以继续验证令牌并验证声明。
您必须解析令牌,并且可以使用包的Parse方法解析令牌jwt。该parse方法接受令牌和 JWT 装饰器函数并返回接口和错误。
您需要使用生成令牌时用于签名令牌的相同签名方法,以使用令牌的Method方法验证签名。在这种情况下,签名方法是ECDSA方法。
token, err := jwt.Parse(request.Header\["Token"\][0], func(token *jwt.Token) (interface{}, error) {
_, ok := token.Method.(*jwt.SigningMethodECDSA)
if !ok {
writer.WriteHeader(http.StatusUnauthorized)
_, err := writer.Write([]byte("You're Unauthorized!"))
if err != nil {
return nil, err
}
}
return "", nil
})
如果签名验证失败(函数返回),您可以向客户端返回一个标头。!ok``StatusUnauthorized
if err != nil {
writer.WriteHeader(http.StatusUnauthorized)
_, err2 := writer.Write([]byte("You're Unauthorized due to error parsing the JWT"))
if err2 != nil {
return
}
}
在上面的代码中,解析令牌时出错。因此,用户是未经授权的,您可以编写消息并返回未经授权的状态。
您可以使用令牌的Valid方法来验证令牌。
if token.Valid {
endpointHandler(writer, request)
} else {
writer.WriteHeader(http.StatusUnauthorized)
_, err := writer.Write([]byte("You're Unauthorized due to invalid token"))
if err != nil {
return
}
}
如果令牌有效,您可以将端点处理程序与处理函数的writer和request参数一起传递给中间件函数以返回端点。
以下是else客户端请求的标头中没有令牌的情况的声明:
else {
writer.WriteHeader(http.StatusUnauthorized)
_, err := writer.Write([]byte("You're Unauthorized due to No token in the header"))
if err != nil {
return
}
}
由于您使用的是中间件,因此路由声明中的处理函数将是verifyJWT中间件,其中路由的处理函数作为参数。
http.HandleFunc("/home", verifyJWT(handlePage))
将验证功能添加到路由后,端点就会通过身份验证。
在客户端,客户端必须提供已发布的令牌。这是一个使用该generateJWT函数在请求中添加令牌的函数。
func authPage(writer http.ResponseWriter, ) {
token, err := generateJWT()
if err != nil {
return
}
client := &http.Client{}
request, _ := http.NewRequest("POST", "<http://localhost:8080/>", nil)
request.Header.Set("Token", token)
_, _ = client.Do(request)
}
在authPage函数中,token变量保存来自函数的标记generateJWT。使用Client对包类型的引用http,您可以创建一个新客户端并向端点发出请求。您还可以选择将令牌设置为 cookie,并在客户端向经过身份验证的端点发出请求时检索它以进行验证。
从 JWT 令牌中提取声明
在生成 JWT 时,您可以选择在令牌中嵌入信息。在generateJWT函数中,您将username变量添加到claims地图中。
以下是提取声明的方法,以username声明为例。验证令牌签名时,您可以使用中间件或将功能添加到验证函数中。
func extractClaims(_ http.ResponseWriter, request *http.Request) (string, error) {
if request.Header["Token"] != nil {
tokenString := request.Header\["Token"\][0]
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
return nil, fmt.Errorf("there's an error with the signing method")
}
return sampleSecretKey, nil
})
if err != nil {
return "Error Parsing Token: ", err
}
}
在extractClaims函数中,过程与verifyJWT函数相同;您从标头中检索了令牌,解析了令牌并验证了签名。
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
username := claims["username"].(string)
return username, nil
}
}
return "unable to extract claims", nil
在验证令牌时,您可以使用该Claims方法检索声明,并使用声明映射来检索 JWT 中的数据,如上所示。
结论
本教程教您如何使用 JWT 身份验证通过使用 JSON Web Tokens 包在 Go 中对 API 和网页端点进行身份验证。您可以在本教程中以GitHub Gist的形式找到完整的代码。golang-jwt
请记住为您的密钥使用环境变量,并且不要在 JWT 中隐藏敏感数据。LogRocket 博客上有许多 JWT 教程,您可以查看这些教程以开始使用您感兴趣的语言或框架!