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. 已过期令牌返回
2. 已吊销令牌返回
3. 伪造令牌返回
4. 不存在的令牌返回
1. 已过期令牌返回
active: false 且无敏感元数据泄露。2. 已吊销令牌返回
active: false。3. 伪造令牌返回
active: false 而非 400 错误。4. 不存在的令牌返回
active: false 而非 404 状态码。
4.5 自省端点流程架构
下图展示了资源服务器通过自省端点验证令牌的完整交互流程:
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 时资源服务器是否拒绝访问。