4、自省端点定义:RFC 7662 规范解读

Token 自省(Introspection)端点是 OAuth 2.0 体系中用于验证令牌当前状态的标准化接口。RFC 7662 定义了该端点的请求/响应格式、语义以及核心字段含义,是构建安全授权测试的基础。

4.1 自省端点核心概念

自省端点允许受保护的资源服务器(RS)向授权服务器(AS)发送一个令牌,并获取该令牌的元数据,包括是否有效、过期时间、所属用户、作用域等。与本地解码 JWT 不同,自省端点适用于不透明令牌(Opaque Token)或需要实时吊销检查的场景。

测试要点: 自省端点通常需要客户端认证(如 client_secret 或私钥 JWT),且应仅允许受信任的资源服务器调用,而非公开给任意客户端。

4.2 自省请求格式

自省请求使用 HTTP POST 方法,请求体为 application/x-www-form-urlencoded 格式,包含以下参数:

参数 是否必需 描述
token 必需 需要自省的令牌字符串(access_token 或 refresh_token)
token_type_hint 可选 提示令牌类型,如 access_token 或 refresh_token,可加速查找
access_token 可选 某些实现中用于携带访问令牌的替代参数名(不推荐)
POST /introspect HTTP/1.1
Host: as.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&token_type_hint=access_token

4.3 自省响应格式

自省响应为 JSON 格式,包含一组令牌元数据字段。核心字段如下:

字段 类型 描述
active boolean 必需。 令牌当前是否有效(未过期、未被吊销)
scope string 令牌授予的作用域,空格分隔
client_id string 令牌所属的客户端标识符
username string 资源所有者(用户)的可读标识
token_type string 令牌类型,如 Bearer
exp integer 令牌过期时间(UNIX 时间戳)
iat integer 令牌签发时间(UNIX 时间戳)
nbf integer 令牌生效时间(UNIX 时间戳)
sub string 令牌主体的唯一标识符(通常是用户 ID)
aud string 或 array 令牌的目标受众
iss string 令牌签发者(授权服务器标识)
jti string 令牌唯一标识符(用于防重放)
HTTP/1.1 200 OK
Content-Type: application/json

{
  "active": true,
  "scope": "read write",
  "client_id": "s6BhdRkqt3",
  "username": "jdoe",
  "token_type": "Bearer",
  "exp": 1718000000,
  "iat": 1717913600,
  "sub": "user_12345",
  "aud": "https://api.example.com",
  "iss": "https://as.example.com",
  "jti": "unique-token-id-abc123"
}

4.4 active 字段的语义与边界

active 字段是自省响应中唯一必须返回的字段,其布尔值决定了资源服务器是否应接受该令牌。理解其语义边界对安全测试至关重要:

  • active: true — 令牌当前有效,资源服务器应信任其携带的所有元数据字段。
  • active: false — 令牌无效,可能原因包括:已过期、已被吊销、格式错误、从未签发、签名验证失败等。此时响应中不应包含其他元数据字段,或仅包含 active 字段。
  • 边界情况: 某些实现可能在 active: false 时仍返回部分字段(如 exp),测试时需验证资源服务器是否忽略这些字段。
安全测试关注点: 测试应覆盖以下场景:
1. 已过期令牌返回 active: false 且无敏感元数据泄露。
2. 已吊销令牌返回 active: false。
3. 伪造令牌返回 active: false 而非 400 错误。
4. 不存在的令牌返回 active: false 而非 404 状态码。

4.5 自省端点流程架构

下图展示了资源服务器通过自省端点验证令牌的完整交互流程:

RFC 7662 自省端点交互流程 客户端 资源服务器 (RS) 授权服务器 (AS) ① 携带令牌请求资源 ② POST 自省请求 自省请求体 token=xxx token_type_hint=access_token Authorization: Basic ... ③ 验证令牌状态 令牌存储查询 过期检查 吊销状态检查 ④ 返回自省响应 自省响应体 { "active": true, "scope": "read write", "exp": 1718000000 } ⑤ 根据 active 字段决策 允许 / 拒绝访问

4.6 自省端点边界测试清单

在 Go Web 授权测试中,针对自省端点的边界验证应覆盖以下场景:

测试类别 测试用例 预期结果
有效令牌 发送一个未过期、未吊销的 access_token active: true,返回完整元数据
过期令牌 发送一个已过期的 access_token active: false,不返回敏感元数据
已吊销令牌 发送一个已被撤销的 access_token active: false
伪造令牌 发送随机字符串或篡改的 JWT active: false,HTTP 200 而非 400
空令牌 发送空字符串或缺失 token 参数 HTTP 400 错误,明确错误描述
重复自省 对同一令牌多次调用自省端点 每次返回一致的结果
未认证请求 不携带客户端认证信息调用自省 HTTP 401 未授权
token_type_hint 错误 发送 access_token 但 hint 设为 refresh_token 仍能正确识别并返回结果

4.7 Go 语言自省客户端实现示例

以下 Go 代码演示了如何调用自省端点并解析响应:

package introspection

import (
    "encoding/json"
    "net/http"
    "net/url"
    "strings"
    "time"
)

// IntrospectionResponse 对应 RFC 7662 自省响应
type IntrospectionResponse struct {
    Active    bool   `json:"active"`
    Scope     string `json:"scope,omitempty"`
    ClientID  string `json:"client_id,omitempty"`
    Username  string `json:"username,omitempty"`
    TokenType string `json:"token_type,omitempty"`
    Exp       int64  `json:"exp,omitempty"`
    Iat       int64  `json:"iat,omitempty"`
    Sub       string `json:"sub,omitempty"`
    Aud       string `json:"aud,omitempty"`
    Iss       string `json:"iss,omitempty"`
    JTI       string `json:"jti,omitempty"`
}

// IntrospectToken 调用自省端点验证令牌
func IntrospectToken(introspectURL, clientID, clientSecret, token string) (*IntrospectionResponse, error) {
    data := url.Values{
        "token":            {token},
        "token_type_hint":  {"access_token"},
    }

    req, err := http.NewRequest("POST", introspectURL, strings.NewReader(data.Encode()))
    if err != nil {
        return nil, err
    }
    req.SetBasicAuth(clientID, clientSecret)
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    client := &http.Client{Timeout: 5 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result IntrospectionResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }
    return &result, nil
}

// IsTokenValid 快速判断令牌是否有效
func IsTokenValid(introspectURL, clientID, clientSecret, token string) bool {
    result, err := IntrospectToken(introspectURL, clientID, clientSecret, token)
    if err != nil {
        return false
    }
    return result.Active
}
测试建议: 在 Go 测试中,应使用 httptest.NewServer 模拟自省端点,验证不同令牌状态下的响应处理逻辑,特别是 active: false 时资源服务器是否拒绝访问。