Golang中如何理解并使用JWT?

Golang中如何理解并使用JWT? 我不理解JWT。 我从网上了解到一些信息:

  • 它提供安全的API调用
  • 确保数据在传输过程中未被篡改
  • 对API调用进行身份验证

但我无法理解的是:

  • 如何让用户知道他的令牌
  • 如何携带令牌向服务器发送请求(POST/GET或其他方式)
  • 是否有其他替代方案?
3 回复

你可能会发现我关于JWT和OpenID Connect的文章很有帮助。

更多关于Golang中如何理解并使用JWT?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


AnikHasibul:

如何让用户知道他的令牌。

JWT 本身并未对此作出规定。它只规定了令牌的格式、包含的内容以及加密或签名的方式。

我构建的一个应用程序采用以下步骤:

  • 用户导航到登录页面
  • 输入用户名和密码,提交登录表单
  • 服务器验证用户名和密码
  • 验证通过后,服务器为用户创建 JWT
  • 服务器在响应中设置 cookie
  • 用户浏览器存储该 cookie,并在后续所有请求中自动发送给服务器
  • 服务器从浏览器发送的 cookie 中提取 JWT
  • 服务器验证 JWT(签名有效、未过期)以确认用户身份认证成功
  • 必要时,服务器会检查 JWT 中的附加声明,以决定是否允许用户访问所请求的资源

我也将 JWT 用于服务端之间的通信。接收服务器的处理流程与上述类似。但请求服务器无法执行登录操作,因此我会生成一个长期有效的 JWT 并将其存储在请求服务器的配置中。

如何携带令牌向服务器发送请求(POST/GET 或其他方式)

有两种方案:

  • 使用 HTTP 头部 Authorization: Bearer <token>
  • 使用 HTTP cookie(同样通过 HTTP 头部发送)

具体从哪个 HTTP 头部获取 JWT 由服务器端决定。

是否有其他替代方案?

据我所知没有。JWT 是一项优秀的技术,具有易于理解、可扩展性强、支持多种编程语言、在 Web 应用中便于使用等特点。

在Go语言中,JWT(JSON Web Token)是一种用于安全传输信息的开放标准,常用于API身份验证和授权。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。下面我将解释JWT的基本概念,并提供Go代码示例来演示如何生成、解析和携带JWT令牌。

JWT的基本理解

  • JWT通过数字签名确保数据的完整性和真实性,防止篡改。它不加密数据本身(除非使用JWE),因此不要在载荷中存储敏感信息。
  • 在身份验证流程中,服务器生成JWT并发送给客户端(例如,通过登录响应)。客户端在后续请求中携带此令牌(通常在HTTP头中),服务器验证令牌以确认用户身份。

如何让用户知道他的令牌

在典型的Web应用中,用户通过登录端点(如/login)提交凭据(如用户名和密码)。服务器验证凭据后,生成JWT并返回给客户端。客户端可以将其存储在本地(如localStorage、cookie或内存中),以便后续使用。例如,在登录响应中返回JSON:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsImV4cCI6MTYzMDAwMDAwMH0.abc123..."
}

如何携带令牌向服务器发送请求

客户端在请求中携带JWT,通常通过HTTP头的Authorization字段,使用Bearer方案。例如:

Authorization: Bearer <token>

在Go中,你可以使用标准库或第三方包(如github.com/golang-jwt/jwt)来处理JWT。以下是一个完整示例,展示生成令牌、验证令牌和在HTTP请求中使用的过程。

示例代码:生成和验证JWT

首先,安装JWT包:

go get github.com/golang-jwt/jwt

然后,编写Go代码:

package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/golang-jwt/jwt"
)

// 定义一个密钥,用于签名和验证JWT。在生产环境中,使用安全的方式存储。
var jwtKey = []byte("my_secret_key")

// 定义JWT的载荷结构
type Claims struct {
    UserID int `json:"user_id"`
    jwt.StandardClaims
}

// 生成JWT令牌的函数
func generateToken(userID int) (string, error) {
    expirationTime := time.Now().Add(24 * time.Hour) // 令牌过期时间为24小时
    claims := &Claims{
        UserID: userID,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: expirationTime.Unix(),
            Issuer:    "test_app",
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtKey)
}

// 验证JWT令牌的函数
func verifyToken(tokenStr string) (*Claims, error) {
    claims := &Claims{}
    token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
        return jwtKey, nil
    })
    if err != nil {
        return nil, err
    }
    if !token.Valid {
        return nil, fmt.Errorf("invalid token")
    }
    return claims, nil
}

// 登录处理函数:模拟用户登录并返回JWT
func loginHandler(w http.ResponseWriter, r *http.Request) {
    // 假设用户验证成功,用户ID为123
    userID := 123
    token, err := generateToken(userID)
    if err != nil {
        http.Error(w, "Failed to generate token", http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"token": "%s"}`, token)
}

// 受保护API的处理函数:验证JWT并返回数据
func protectedHandler(w http.ResponseWriter, r *http.Request) {
    // 从Authorization头中提取令牌
    authHeader := r.Header.Get("Authorization")
    if authHeader == "" {
        http.Error(w, "Authorization header missing", http.StatusUnauthorized)
        return
    }
    // 期望格式:Bearer <token>
    if len(authHeader) < 7 || authHeader[:7] != "Bearer " {
        http.Error(w, "Invalid authorization format", http.StatusUnauthorized)
        return
    }
    tokenStr := authHeader[7:]

    claims, err := verifyToken(tokenStr)
    if err != nil {
        http.Error(w, "Invalid token", http.StatusUnauthorized)
        return
    }
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Access granted for user ID: %d", claims.UserID)
}

func main() {
    http.HandleFunc("/login", loginHandler)
    http.HandleFunc("/protected", protectedHandler)
    http.ListenAndServe(":8080", nil)
}

在这个示例中:

  • generateToken 函数基于用户ID生成JWT,使用HS256算法签名。
  • verifyToken 函数解析并验证JWT,如果有效则返回载荷。
  • loginHandler 处理登录请求,返回JWT令牌。
  • protectedHandler 是一个受保护的端点,它检查请求头中的JWT,验证通过后才允许访问。

客户端在登录后获取令牌,然后在请求受保护资源时,在HTTP头中添加Authorization: Bearer <token>。你可以使用工具如curl测试:

# 登录获取令牌
curl -X POST http://localhost:8080/login

# 使用令牌访问受保护端点(替换<token>为实际令牌)
curl -H "Authorization: Bearer <token>" http://localhost:8080/protected

是否有其他替代方案?

是的,JWT有多种替代方案,具体选择取决于应用场景:

  • Session-Based Authentication:使用服务器端存储的会话ID(例如,通过cookie)。优点是服务器可以主动撤销会话,但需要状态存储。
  • OAuth 2.0 / OpenID Connect:适用于第三方授权和单点登录(SSO),更复杂但功能丰富。
  • API Keys:简单但安全性较低,适用于内部或低风险场景。
  • SAML:基于XML的标准,常用于企业级SSO。

在Go中,你可以根据需求选择这些方案。例如,对于会话认证,可以使用gorilla/sessions包;对于OAuth,可以使用golang.org/x/oauth2包。

总之,JWT在无状态API中非常高效,但需注意令牌过期和撤销机制。通过以上示例,你可以开始在Go项目中实现JWT身份验证。

回到顶部