Token 简介
在应用开发中,使用令牌(Token)是一种常见的身份验证和授权机制。以下是一些使用令牌的主要原因:
- 安全性: 令牌是一种安全的身份验证方式。相比于传统的用户名和密码验证方式,令牌可以更好地保护用户的凭证信息。通过使用令牌,应用可以在不传递用户凭证的情况下完成身份验证。
- 无状态性: 令牌机制使得服务器可以在不保存用户状态的情况下完成身份验证。每个请求都携带了足够的信息(令牌)来进行身份验证和授权,而不需要在服务器端保存大量的用户信息。
- 跨平台和跨服务: 由于令牌是一种标准化的身份验证机制,它可以被用于跨平台和跨服务的身份验证。一个令牌可以在多个服务之间传递,而不需要每个服务都保存用户凭证。
- 授权: 令牌不仅可以用于身份验证,还可以包含有关用户的授权信息。通过在令牌中添加一些声明(claims),可以实现细粒度的授权,确保用户只能访问其有权限的资源。
- 易于集成: 多数开发框架和第三方服务都提供了对令牌的支持。这使得开发者可以方便地将令牌集成到他们的应用中,而无需从头开始实现身份验证系统。
- 可调整的过期时间: 令牌通常具有过期时间,这使得安全性得到提高。即使令牌被截获,由于其过期,攻击者也只能在有限的时间内使用。
- 减轻密码管理: 对于移动应用或第三方应用,令牌可以用于避免存储用户的敏感信息(如密码)。用户只需提供一次凭证,然后获得一个令牌,之后的请求都使用令牌进行身份验证。
JWT 介绍
JSON Web Token(JWT)是一种用于在网络上安全传输声明的一种开放标准(RFC 7519)。它由一串经过 Base64 编码的 JSON 对象组成,可以包含用户的一些身份信息,以便在不同系统之间安全传输。
JWT 主要由三个部分组成:
- Header(头部): 头部通常由两部分组成,
alg
表示签名算法(HMAC SHA256、RSA等),typ
表示令牌类型,这两部分会被 Base64 编码。 - Payload(载荷): 载荷包含了一些声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。有三种类型的声明:
- 注册声明(Registered claims): 这些声明是预定义的,不是强制要求的,但被推荐使用。例如,
iss
表示令牌的发行者,sub
表示令牌的主题,exp
表示令牌的过期时间等。 - 公共声明(Public claims): 这些声明被定义为在 JWT 中定义的标准化名称,但可以根据需要定义新的声明。
- 私有声明(Private claims): 这些是自定义声明,供应用程序使用,不会与 JWT 的标准冲突。
- 注册声明(Registered claims): 这些声明是预定义的,不是强制要求的,但被推荐使用。例如,
- Signature(签名): 签名部分由编码后的头部、编码后的载荷以及一个秘钥共同组成,用于验证消息的完整性。签名的创建过程:
- 将编码后的头部和编码后的载荷用点号连接起来,形成未加密的 JWT。
- 使用指定的算法(如 HMAC SHA256)和秘钥对未加密的 JWT 进行签名。
JWT 的主要用途是在用户和服务器之间传递安全的身份信息。由于其轻量且易于使用,它已成为许多身份验证和授权协议的标准。
由于 JWT 的载荷(Payload)信息是 Base64 编码的,所以不应该在 JWT 中放置敏感信息,例如密码等。
实现示例
对接第三方 API 通常涉及到以下几个步骤:获取访问令牌(token)、使用令牌进行 API 请求、处理 API 响应,以及在需要时刷新令牌。下面是一个简单的示例,演示如何使用github.com/golang-jwt/jwt/v5
库在 Go 中实现请求token
、刷新token
以及封装请求:
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
)
// SecretKey 用于签名和验证的密钥
var SecretKey = []byte("your-secret-key")
var expire = 30 * time.Minute
// GenerateToken 生成 JWT
func GenerateToken() (string, error) {
claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expire)),
Issuer: "example",
Subject: "example",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, err := token.SignedString(SecretKey)
if err != nil {
return "", err
}
return signedToken, nil
}
// RefreshToken 刷新 JWT
func RefreshToken(tokenString string) (string, error) {
// 解析 token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return SecretKey, nil
})
if err != nil || !token.Valid {
return "", fmt.Errorf("invalid token")
}
// 验证 token 是否有效
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return "", fmt.Errorf("invalid claims")
}
exp, err := claims.GetExpirationTime()
if err != nil {
return "", errors.Wrap(err, "GetExpirationTime from token error")
}
if time.Until(exp.Time) < 0 {
return "", errors.New("the token expires")
}
return GenerateToken()
}
// AuthMiddleware 中间件,用于验证请求中的 token
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 解析 token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return SecretKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 将解析后的用户信息存储在上下文中,以便后续处理函数使用
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "user", claims["sub"])
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
// ExampleHandler 示例处理程序,需要通过 AuthMiddleware 进行身份验证
func ExampleHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user")
w.Write([]byte(fmt.Sprintf("Hello, %s!", user)))
}
func main() {
// 示例代码中使用的路由是伪代码,请根据你的实际项目使用适当的路由设置
mux := http.NewServeMux()
// 处理 /login 路径,生成 token
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
token, err := GenerateToken()
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
w.Write([]byte(token))
})
// 处理 /refresh 路径,刷新 token
mux.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
newToken, err := RefreshToken(tokenString)
if err != nil {
http.Error(w, "Failed to refresh token", http.StatusInternalServerError)
return
}
w.Write([]byte(newToken))
})
// 处理其他路径,需要通过 AuthMiddleware 进行身份验证
mux.Handle("/example", AuthMiddleware(http.HandlerFunc(ExampleHandler)))
fmt.Println("service start")
http.ListenAndServe(":8080", mux)
}
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意
腾讯云开发者社区:孟斯特