Documentation
¶
Overview ¶
Package jwtx 提供独立的 HS256、RS256、ES256、PS256 JWT 签发器与验签器,复用 golang-jwt/jwt v5。 实例固定算法与密钥并可并发使用;声明的业务含义、刷新会话、撤销与 HTTP 适配由调用方负责。
以下示例需导入 crypto/rand、time、github.com/golang-jwt/jwt/v5 和 github.com/bpcoder16/pixiu/infra/jwtx。生产环境应从安全配置读取持久密钥, 下例临时生成密钥仅用于展示调用:
secret := make([]byte, 32)
if _, err := rand.Read(secret); err != nil {
return err
}
signer, err := jwtx.NewSigner(jwtx.SignConfig{
Algorithm: jwtx.HS256,
Secret: secret,
})
if err != nil {
return err
}
verifier, err := jwtx.NewVerifier(jwtx.VerifyConfig{
Algorithm: jwtx.HS256,
Secret: secret,
Issuer: "example",
Audience: []string{"app"},
})
if err != nil {
return err
}
now := time.Now()
raw, err := signer.Sign(jwt.RegisteredClaims{
Issuer: "example",
Subject: "user-42",
Audience: jwt.ClaimStrings{"app"},
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(15 * time.Minute)),
})
if err != nil {
return err
}
var claims jwt.RegisteredClaims
if err := verifier.Verify(raw, &claims); err != nil {
return err
}
// 仅在 Verify 成功后使用 claims.Subject 等身份字段。
return nil
自定义 claims 推荐按值嵌入 jwt.RegisteredClaims,保留自身 JSON 布局。 Sign 要求 exp 存在但不修改或验证业务声明;Verify 始终要求 exp 并检查时间。 每次验签须使用新的空 claims 对象,出错时丢弃可能已解码的不可信内容。 可使用 errors.Is(err, jwt.ErrTokenExpired) 等判断底层错误,HTTP 错误码由应用映射。 HS256 密钥至少 32 字节;RS256、PS256 使用至少 2048 位的 RSA 密钥。 ES256 只接受 P-256,私钥支持 SEC1 / PKCS#8 PEM,公钥支持 PKIX / 证书 PEM。 PS256 沿用 RSA PEM 格式,签发和验签均要求标准的 32 字节盐长度。 非对称算法通过 PrivateKeyPEM / PublicKeyPEM 配置,不传 Secret;验签器只需公钥。 证书 PEM 仅提取公钥,不校验证书链或证书有效期。
Example ¶
package main
import (
"crypto/rand"
"errors"
"fmt"
"time"
"github.com/bpcoder16/pixiu/infra/jwtx"
"github.com/golang-jwt/jwt/v5"
)
func main() {
// 生产环境从安全配置读取持久密钥;这里临时生成用于展示。
secret := make([]byte, 32)
if _, err := rand.Read(secret); err != nil {
panic(err)
}
signer, err := jwtx.NewSigner(jwtx.SignConfig{
Algorithm: jwtx.HS256,
Secret: secret,
})
if err != nil {
panic(err)
}
verifier, err := jwtx.NewVerifier(jwtx.VerifyConfig{
Algorithm: jwtx.HS256,
Secret: secret,
Issuer: "example",
Audience: []string{"app"},
})
if err != nil {
panic(err)
}
type appClaims struct {
UserID string `json:"uid"`
Portal string `json:"portal"`
jwt.RegisteredClaims
}
now := time.Now()
raw, err := signer.Sign(appClaims{
UserID: "user-42",
Portal: "app",
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "example",
Audience: jwt.ClaimStrings{"app"},
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(15 * time.Minute)),
},
})
if err != nil {
panic(err)
}
var claims appClaims
if err = verifier.Verify(raw, &claims); err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
fmt.Println("token 已过期")
}
return
}
fmt.Println(claims.UserID, claims.Portal)
}
Output: user-42 app
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Algorithm ¶
type Algorithm string
Algorithm 是实例固定使用的签名算法。 性能须分别比较签发和验签,并考虑 Go 版本、CPU、密钥长度及 claims 大小。 以下性能描述基于本机基准,环境与数据见 docs/jwtx-design.md;可运行 BenchmarkJWTAlgorithms 复测。
const ( // HS256 使用 HMAC-SHA256;本机基准中签发、验签的耗时均最低。 // 使用共享密钥,持有验签密钥的一方也具备签发能力。 HS256 Algorithm = "HS256" // RS256 使用 RSA PKCS#1 v1.5;本机 2048 位 RSA 基准中签发成本明显高于验签。 // RSA 密钥长度影响运算成本,不能将 2048 位基准直接用于更长的密钥。 RS256 Algorithm = "RS256" // ES256 使用 P-256 ECDSA;本机基准中签发快于 2048 位 RSA,验签慢于 RSA。 // 原始签名为 64 字节,小于 2048 位 RSA 的 256 字节,可减少 Token 传输体积。 ES256 Algorithm = "ES256" // PS256 使用 RSA-PSS;同一把 2048 位 RSA 密钥下,本机签发、验签成本与 RS256 接近。 // PSS 另有随机盐和编码处理,不应假定它与 RS256 存在固定的性能排序。 PS256 Algorithm = "PS256" )
type SignConfig ¶
type SignConfig struct {
// Algorithm 必填,无默认值,不根据密钥类型自动推断。
// 支持 HS256(HMAC)、RS256(RSA PKCS#1 v1.5)、ES256(ECDSA)、PS256(RSA-PSS),均使用 SHA-256。
Algorithm Algorithm
// Secret 仅在 HS256 时必填,至少 32 字节;此时 PrivateKeyPEM 必须留空。
// RS256、ES256、PS256 不接受 Secret,必须留空(nil 或长度为 0)。
// 接收原始密钥字节;配置中若保存的是 Base64 或十六进制文本,须由调用方先解码。
// 生产环境应使用安全随机生成并持久保存的密钥,启动时读取,不应每次启动重新生成。
Secret []byte
// PrivateKeyPEM 在 RS256、ES256、PS256 时必填;此时 Secret 必须留空。
// HS256 不接受 PrivateKeyPEM,必须留空(nil 或长度为 0)。
// RS256、PS256:RSA 私钥,至少 2048 位,支持 PKCS#1 / PKCS#8 PEM。
// ES256:ECDSA 私钥,必须使用 P-256 曲线,支持 SEC1 / PKCS#8 PEM。
// 接收完整、未加密的私钥 PEM 内容,不能传入公钥或文件路径。
PrivateKeyPEM []byte
}
SignConfig 配置签发器;必须显式指定算法及其对应的密钥,不自动生成密钥。 密钥由调用方从安全配置或文件读取;字段接收密钥内容,不接受文件路径。 缺少必填字段、传入该算法不接受的非空密钥字段或密钥无效时,NewSigner 返回错误。
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer 是可并发复用的签发器;零值不可用,须经 NewSigner 创建。
func NewSigner ¶
func NewSigner(cfg SignConfig) (*Signer, error)
NewSigner 校验配置并复制或解析密钥,不保留配置切片。
Example (Asymmetric) ¶
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"time"
"github.com/bpcoder16/pixiu/infra/jwtx"
"github.com/golang-jwt/jwt/v5"
)
func main() {
// 示例生成临时密钥;实际应用从安全配置读取持久 PEM。
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
for _, config := range []struct {
algorithm jwtx.Algorithm
key crypto.Signer
}{
{
algorithm: jwtx.ES256,
key: ecKey,
},
{
algorithm: jwtx.PS256,
key: rsaKey,
},
} {
privateDER, err := x509.MarshalPKCS8PrivateKey(config.key)
if err != nil {
panic(err)
}
publicDER, err := x509.MarshalPKIXPublicKey(config.key.Public())
if err != nil {
panic(err)
}
signer, err := jwtx.NewSigner(jwtx.SignConfig{
Algorithm: config.algorithm,
PrivateKeyPEM: pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: privateDER,
}),
})
if err != nil {
panic(err)
}
verifier, err := jwtx.NewVerifier(jwtx.VerifyConfig{
Algorithm: config.algorithm,
PublicKeyPEM: pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicDER,
}),
Issuer: "auth",
Audience: []string{"app"},
})
if err != nil {
panic(err)
}
raw, err := signer.Sign(jwt.RegisteredClaims{
Issuer: "auth",
Subject: "user-42",
Audience: jwt.ClaimStrings{"app"},
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
})
if err != nil {
panic(err)
}
var claims jwt.RegisteredClaims
if err = verifier.Verify(raw, &claims); err != nil {
panic(err)
}
fmt.Println(config.algorithm, claims.Subject)
}
}
Output: ES256 user-42 PS256 user-42
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier 是可并发复用的验签器;零值不可用,须经 NewVerifier 创建。
func NewVerifier ¶
func NewVerifier(cfg VerifyConfig) (*Verifier, error)
NewVerifier 解析密钥并固定校验策略;证书 PEM 仅用于提取公钥。
type VerifyConfig ¶
type VerifyConfig struct {
// Algorithm 必填,无默认值,不根据密钥类型或 token 中的 alg 自动推断。
// 支持 HS256、RS256、ES256、PS256;token 中的 alg 必须与此配置精确匹配。
Algorithm Algorithm
// Secret 仅在 HS256 时必填,至少 32 字节;此时 PublicKeyPEM 必须留空。
// 必须与签发器使用相同的原始密钥字节;Base64 或十六进制文本须由调用方先解码。
// RS256、ES256、PS256 不接受 Secret,必须留空(nil 或长度为 0)。
Secret []byte
// PublicKeyPEM 在 RS256、ES256、PS256 时必填;此时 Secret 必须留空。
// HS256 不接受 PublicKeyPEM,必须留空(nil 或长度为 0)。
// RS256、PS256:RSA 公钥,至少 2048 位,支持 PKIX / PKCS#1 PEM。
// ES256:ECDSA 公钥,必须使用 P-256 曲线,支持 PKIX PEM。
// 均支持证书 PEM,但仅提取公钥,不校验证书链或证书有效期。
// 接收与签发私钥配对的完整公钥或证书 PEM 内容,不能传入私钥或文件路径。
PublicKeyPEM []byte
// Issuer 非空时要求 iss 存在且精确匹配;为空时不校验签发者。
// 不接受仅含空白的配置值,不自动去除首尾空白。
Issuer string
// Audience 非空时要求 aud 至少匹配其中一个值;为空时不校验接收方。
// 每个元素均不得为空字符串或仅含空白,不自动去除首尾空白。
Audience []string
// Leeway 是时间校验允许的偏差,默认 0,不得为负。
// 用于 exp、nbf,以及开启 CheckIssuedAt 后的 iat 校验,以容忍时钟偏差。
Leeway time.Duration
// CheckIssuedAt 默认 false;开启后检查已有 iat 不晚于当前时间(计入 Leeway)。
// 不要求 iat 存在,也不会自动补写签发时间。
CheckIssuedAt bool
}
VerifyConfig 配置验签器;exp 始终必需,算法与密钥在构造后固定。 密钥由调用方从安全配置或文件读取;字段接收密钥内容,不接受文件路径。 缺少必填字段、传入该算法不接受的非空密钥字段或配置无效时,NewVerifier 返回错误。