61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
// jwt.go agent 登录令牌的 JWT 校验与签发(HS256)。
|
||
// 正式部署由签发方(如 cloud)生成 token 下发给各隧道;节点只做校验。
|
||
// SignToken 供 cmd/node -print-token(运维手工签发)与测试使用。
|
||
package node
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/golang-jwt/jwt/v5"
|
||
)
|
||
|
||
// verifyToken 校验 agent 登录 JWT:
|
||
// - HS256/384/512 签名(显式白名单,拒绝 none 与其它算法)
|
||
// - 必须带 exp 且未过期
|
||
// - 若带 agentId 或 sub claim,则必须与登录 AgentId 一致(防跨 agent 冒用)
|
||
func verifyToken(token, secret, agentId string) error {
|
||
if secret == "" {
|
||
return errors.New("节点未配置JWT密钥")
|
||
}
|
||
parsed, err := jwt.Parse(token, func(t *jwt.Token) (any, error) {
|
||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||
return nil, fmt.Errorf("签名算法不允许:%v", t.Header["alg"])
|
||
}
|
||
return []byte(secret), nil
|
||
}, jwt.WithValidMethods([]string{
|
||
jwt.SigningMethodHS256.Alg(),
|
||
jwt.SigningMethodHS384.Alg(),
|
||
jwt.SigningMethodHS512.Alg(),
|
||
}), jwt.WithExpirationRequired())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
claims, ok := parsed.Claims.(jwt.MapClaims)
|
||
if !ok {
|
||
return errors.New("claims类型错误")
|
||
}
|
||
for _, k := range []string{"agentId", "sub"} {
|
||
if v, ok := claims[k].(string); ok && v != "" && v != agentId {
|
||
return fmt.Errorf("token %s 与登录agentId不一致", k)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// SignToken 用节点密钥签发一张 agent 登录 JWT。
|
||
// ttl<=0 会得到立即过期的 token(仅测试构造过期样例时有意义)。
|
||
func SignToken(secret, agentId string, ttl time.Duration) (string, error) {
|
||
if secret == "" {
|
||
return "", errors.New("未配置JWT密钥")
|
||
}
|
||
now := time.Now()
|
||
claims := jwt.MapClaims{
|
||
"agentId": agentId,
|
||
"iat": now.Unix(),
|
||
"exp": now.Add(ttl).Unix(),
|
||
}
|
||
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
|
||
}
|