Golang中的JWT介绍 | 青训营

321 阅读6分钟

1. JWT的定义及作用

JSON Web Token(JWT)是一种开放标准,用于在双方之间安全地传输信息。它是一种基于令牌的认证和授权机制,使用JSON格式表示。在Golang中,JWT被广泛应用于身份验证和授权等场景,它能够确保信息在传输过程中安全且不被篡改。

2. JWT的三个部分

JWT主要由三部分组成:头部(Header)、负载(Payload)和签名(Signature)。

  • 头部(Header): 头部包含一个标识算法和一个类型声明,表示该令牌使用哪种算法进行加密和签名。常见的算法有HS256、RS256和ES256等。
  • 负载(Payload): 负载包含一组声明,其中包含有关令牌的元信息。这些声明使用JSON格式编码,可以传递更多的信息,如用户信息、角色信息等。
  • 签名(Signature): 签名是使用头部和负载作为输入,通过一个算法(如HMAC或RSA)生成的加密字符串。这个签名用于验证令牌的完整性和真实性。

以下是一个使用Golang创建和验证JWT的示例代码:

package main

import (
 "crypto/hmac"
 "crypto/sha256"
 "encoding/hex"
 "encoding/json"
 "fmt"
 "time"
)

// Header结构体表示JWT的头部信息
type Header struct {
 Algorithm string `json:"alg"`
 Type      string `json:"typ"`
}

// Claim结构体表示JWT的负载信息
type Claim struct {
 Issuer   string `json:"iss"`
 Subject  string `json:"sub"`
 Audience string `json:"aud"`
 Expires  int64  `json:"exp"`
 NotBefore int64  `json:"nbf"`
 IssuedAt int64  `json:"iat"`
 Jti      string `json:"jti"`
}

// CreateToken函数用于生成JWT
func CreateToken(payload []byte, secretKey string) (string, error) {
 header, err := json.Marshal(Header{Algorithm: "HS256", Type: "JWT"})
 if err != nil {
 return "", err
 }
 hash := hmac.New(sha256.New, []byte(secretKey))
 hash.Write(header)
 hash.Write(payload)
 signature := hash.Sum(nil)
 return fmt.Sprintf("%s.%s.%s", header, payload, hex.EncodeToString(signature)), nil
}

// ValidateToken函数用于验证JWT的有效性
func ValidateToken(token string, secretKey string) error {
 parts := strings.Split(token, ".")
 if len(parts) != 3 {
 return fmt.Errorf("Invalid token format")
 }
 header, err := base64.StdEncoding.DecodeString(parts[0])
 if err != nil {
 return fmt.Errorf("Invalid header")
 }
 payload, err := base64.StdEncoding.DecodeString(parts[1])
 if err != nil {
 return fmt.Errorf("Invalid payload")
 }
 signature := parts[2]
 hash := hmac.New(sha256.New, []byte(secretKey))
 hash.Write([]byte(header))
 hash.Write([]byte(payload))
 calculatedSignature := hash.Sum(nil)
 if !hmac.Equal([]byte(signature), calculatedSignature) {
 return fmt.Errorf("Invalid signature")
 }
 // 验证时间戳等其他信息,确保令牌有效,此处简化为直接返回成功。
 return nil
}

3. JWT的签发与验证

在JWT(JSON Web Token)的生成和验证过程中,我们主要使用两种算法:HS256和RS256。下面我们将详细解释这两种算法的应用。

JWT的生成

在生成JWT时,我们可以使用HS256或RS256算法。

HS256

在HS256算法中,发送方使用密钥(secret key)对数据进行加密。以下是一个使用Go语言生成HS256 JWT的示例:

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "time"
)

func main() {
    key := []byte("your-secret-key")
    payload := map[string]interface{}{
        "sub": "1234567890",
        "name": "John Doe",
        "iat": time.Now().Unix(),
    }

    token, err := signHS256(key, payload)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(token)
}

func signHS256(key []byte, payload map[string]interface{}) (string, error) {
    payloadJson, err := json.Marshal(payload)
    if err != nil {
        return "", err
    }
    mac := hmac.New(sha256.New, key)
    mac.Write(payloadJson)
    signature := mac.Sum(nil)
    signature = append(signature, []byte(".")...)
    signatureJson, err := json.Marshal(signature)
    if err != nil {
        return "", err
    }
    return fmt.Sprintf("%s.%s", string(payloadJson), string(signatureJson)), nil
}

RS256

在RS256算法中,发送方使用公钥对数据进行加密。以下是一个使用Go语言生成RS256 JWT的示例:

package main

import (
    "crypto/x509"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/pem"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "time"
)

func main() {
    key, _ := ioutil.ReadFile("path/to/private.key") // 读取您的私钥文件
    block, _ := pem.Decode(key) // 解码私钥以获取公钥和私钥的详细信息
    privateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes) // 使用私钥类型解析私钥详情,如果您使用的是RSA密钥,则应使用x509.ParsePKCS8PrivateKey()函数来解析私钥详情。
    payload := map[string]interface{}{
        "sub": "1234567890",
        "name": "John Doe",
        "iat": time.Now().Unix(),
    }
    token, err := signRS256(privateKey, payload) // 使用私钥对令牌进行签名并返回令牌字符串。如果发生错误,则返回错误。请确保您已正确加载私钥文件并正确解析私钥详情。请注意,此示例中的错误处理非常简单,您可能需要根据您的需求进行更详细的错误处理。

JWT的验证

验证JWT时,我们需要检查签发者(issuer)、主题(subject)和有效时间。如果JWT被成功验证,我们可以解码其内容以获取用户的身份信息。

package main
import (
    "crypto/x509"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/pem"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "time"
)
func main() {
    token := "your-jwt-token" // 您的JWT令牌
    // 验证JWT
    _, err := decodeRS256(token)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("JWT验证成功!")
}
func decodeRS256(token string) (*rsa.PublicKey, error) {
    // 解码JWT的头部和负载部分
    headerAndPayload := token
    splitToken := strings.Split(headerAndPayload, ".")
    if len(splitToken) < 2 {
        return nil, errors.New("无效的JWT")
    }
    header, err := base64.RawURLEncoding.DecodeString(splitToken[0])
    if err != nil {
        return nil, errors.New("无效的JWT头部")
    }
    payload, err := base64.RawURLEncoding.DecodeString(splitToken[1])
    if err != nil {
        return nil, errors.New("无效的JWT负载")
    }
    var headers map[string]interface{} // 将头部解析为JSON对象
    err = json.Unmarshal(header, &headers) // 将头部解析为JSON对象,并检查签发者(issuer)和主题(subject)是否匹配。如果匹配,则返回公钥。
    if err != nil {
        return nil, errors.New("无效的JWT头部")
    }
    issuer, ok := headers["iss"] // 获取签发者(issuer)字段的值
    if !ok || issuer != "your-issuer" { // 检查签发者是否匹配,如果不匹配,则返回无效的JWT错误。
        return nil, errors.New("无效的JWT")
    }
    subject, ok := headers["sub"] // 获取主题(subject)字段的值
    if !ok || subject != "your-subject" { // 检查主题是否匹配,如果不匹配,则返回无效的JWT错误。
        return nil, errors.New("无效的JWT")
    }
    // 解码JWT的签名部分并使用公钥进行验证。如果验证成功,则返回公钥。否则返回无效的JWT错误。

4. JWT的应用场景

JWT在单点登录、用户信息验证和API安全认证等方面具有广泛的应用。通过将用户信息存储在JWT中,可以将用户身份信息传递给不同的系统,实现单点登录功能。同时,JWT还可以用于验证用户身份和授权,保护用户数据和API接口安全。

5. JWT的优缺点

JWT的优点如下:

  • 开放标准:JWT是一个开放标准,具有广泛的支持和应用。
  • 跨语言、跨平台:JWT可以在不同的编程语言和平台上使用,具有良好的兼容性。
  • 体积小,传输速度快:由于JWT使用了JSON格式,因此其体积相对较小,传输速度较快。
  • 可用于加密、解密、签名、验证:JWT具有多种用途和应用场景,包括加密、解密、签名和验证等。

然而,JWT也存在一些缺点:

  • 可能遭受某些安全漏洞攻击:例如,可能存在某些漏洞攻击如中间人攻击和暴力破解攻击等。
  • 性能消耗较大:使用JWT时,需要对每个令牌进行解密和验证,这可能会对性能造成一定的开销。