JSON Web令牌(JWT)是处理在线认证的一种流行方法,你可以在任何服务器端编程语言中实现JWT认证。
对于阅读JWT的一般背景,我建议通过LogRocket博客上的这些文章了解更多关于JWT、最佳实践以及用JWT保护RESTful API的信息。
本文旨在帮助您在Go Web应用程序中使用golang-jwt 包开始实施JWT认证。
包 [golang-jwt](https://github.com/golang-jwt/jwt)包是在Go中实现JWT的最流行的包,因为它的功能和使用方便。golang-jwt 包提供了生成和验证JWT的功能。
前提条件
您需要满足这些基本要求,才能从本教程中获得最大收益:
- 在您的机器上安装 Go 1.16 或更高版本(出于安全考虑)。
- 有使用 Go 或其他语言构建 Web 应用程序的经验(可选)。
开始使用 Golang-JWT 包
在设置好 Go 工作区并初始化 Go 模块文件go.mod 之后,在工作区目录下的终端上运行此命令以安装golang-jwt 包:
go get github.com/golang-jwt/jwt
一旦你安装了golang-jwt ,创建一个Go文件并导入这些包和模块:
import (
"log"
"encoding/json"
"github.com/golang-jwt/jwt"
"net/http"
"time"
)
在本教程中,你将使用这些包来记录错误,建立一个服务器,并设置令牌的过期时间。
在Go中设置一个网络服务器
让我们先创建一个简单的网络服务器,它的端点将用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 函数将使用JWTs保护页面。服务器被设置为监听端口:8080 ,但你可以使用你选择的任何端口。
handlePage 处理函数将返回Message 结构的编码 JSON 作为对客户端的响应,如果请求主体被编码后请求被授权:
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 函数,在这一点上,并没有被认证,向页面发出请求将自由地工作。在本教程的后面,你将学习如何为你的处理函数添加认证。

使用Golang-JWT 包生成用于认证的JWTs
你将需要一个秘钥来使用golang-jwt 包生成JWT令牌。这里有一个本教程的私钥例子;然而,你应该使用一个加密安全的字符串作为你的秘钥,并从环境变量文件(.env)中加载它。
请查看这篇文章,了解如何在你的Go应用程序中使用环境变量:
var sampleSecretKey = []byte("SecretYouShouldHide")
请注意,无论谁拥有你用于JWTs的秘钥,都可以验证你的应用程序的用户。在这种情况下,sampleSecretKey 变量持有私钥。
这里有一个生成JWT令牌的函数。该函数应该返回一个字符串和一个错误。如果在生成JWT时有错误,该函数会返回一个空字符串和错误。如果没有错误,该函数会返回JWT字符串和nil 类型:
func generateJWT() (string, error) {
}
你可以使用JWT包的New 方法创建一个新的令牌。New 方法接收一个签名方法(JWT的加密算法)并返回一个JWT令牌:
token := jwt.New(jwt.SigningMethodEdDSA)
如果你想修改JWT,你可以使用该令牌的Claims 方法:
claims := token.Claims.(jwt.MapClaims)
claims["exp"] = time.Now().Add(10 * time.Minute)
claims["authorized"] = true
claims["user"] = "username"
在这种情况下,你要为JWT设置一个过期时间,即十分钟,使用time 模块以及用户名和授权状态。在试图验证JWT时,你就可以检索到索赔。
生成JWT的最后一部分是使用你的秘钥来签署字符串。你可以使用令牌的SignedString 方法签署你的令牌字符串。SignedString 方法接受秘密密钥并返回一个签名的令牌字符串:
tokenString, err := token.SignedString(sampleSecretKey)
if err != nil {
return "", err
}
return tokenString, nil
在签署令牌有错误的情况下,你可以返回一个空字符串和错误。
与cookies不同,你不需要存储JWT;你需要的只是你的签署密钥来验证令牌。
验证JWT令牌
验证JWT的传统方法是使用中间件(处理函数,接收其他处理函数进行操作)。下面是如何使用中间件来验证一个请求是否被授权:
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 {
}
如果有一个令牌,你就可以继续验证令牌和验证索赔。
你必须解析令牌,你可以使用jwt 包的Parse 方法解析令牌。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 函数的令牌。使用对http 包的Client 类型的引用,你可以创建一个新的客户端并向端点发出请求。request 变量是请求实例,并且--使用请求实例的Set 方法的header 方法--你可以在请求头中设置令牌,如上所示。
你也可以选择将令牌设置为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认证,通过使用golang-jwt 包,用JSON Web Tokens在Go中认证你的API和网页端点。你可以在Github Gist上找到本教程的完整代码。
记住,要使用环境变量作为你的秘钥,不要在JWT中隐藏敏感数据。LogRocket博客上有许多JWT教程,你可以查看,以开始使用你感兴趣的语言或框架