Token 简介

在应用开发中,使用令牌(Token)是一种常见的身份验证和授权机制。以下是一些使用令牌的主要原因:

  1. 安全性: 令牌是一种安全的身份验证方式。相比于传统的用户名和密码验证方式,令牌可以更好地保护用户的凭证信息。通过使用令牌,应用可以在不传递用户凭证的情况下完成身份验证。
  2. 无状态性: 令牌机制使得服务器可以在不保存用户状态的情况下完成身份验证。每个请求都携带了足够的信息(令牌)来进行身份验证和授权,而不需要在服务器端保存大量的用户信息。
  3. 跨平台和跨服务: 由于令牌是一种标准化的身份验证机制,它可以被用于跨平台和跨服务的身份验证。一个令牌可以在多个服务之间传递,而不需要每个服务都保存用户凭证。
  4. 授权: 令牌不仅可以用于身份验证,还可以包含有关用户的授权信息。通过在令牌中添加一些声明(claims),可以实现细粒度的授权,确保用户只能访问其有权限的资源。
  5. 易于集成: 多数开发框架和第三方服务都提供了对令牌的支持。这使得开发者可以方便地将令牌集成到他们的应用中,而无需从头开始实现身份验证系统。
  6. 可调整的过期时间: 令牌通常具有过期时间,这使得安全性得到提高。即使令牌被截获,由于其过期,攻击者也只能在有限的时间内使用。
  7. 减轻密码管理: 对于移动应用或第三方应用,令牌可以用于避免存储用户的敏感信息(如密码)。用户只需提供一次凭证,然后获得一个令牌,之后的请求都使用令牌进行身份验证。

JWT 介绍

JSON Web Token(JWT)是一种用于在网络上安全传输声明的一种开放标准(RFC 7519)。它由一串经过 Base64 编码的 JSON 对象组成,可以包含用户的一些身份信息,以便在不同系统之间安全传输。

JWT 主要由三个部分组成:

  1. Header(头部): 头部通常由两部分组成,alg 表示签名算法(HMAC SHA256、RSA等),typ 表示令牌类型,这两部分会被 Base64 编码。
  2. Payload(载荷): 载荷包含了一些声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。有三种类型的声明:
    • 注册声明(Registered claims): 这些声明是预定义的,不是强制要求的,但被推荐使用。例如,iss 表示令牌的发行者,sub 表示令牌的主题,exp 表示令牌的过期时间等。
    • 公共声明(Public claims): 这些声明被定义为在 JWT 中定义的标准化名称,但可以根据需要定义新的声明。
    • 私有声明(Private claims): 这些是自定义声明,供应用程序使用,不会与 JWT 的标准冲突。
  3. 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: 恋水无意
腾讯云开发者社区:孟斯特