令牌断言服务负责什么?
已配置的断言服务验证令牌,并返回显式主体及其身份域,再由 Speedle 使用这些身份评估策略。生产实现必须验证签名或 introspection 结果、签发者、受众、有效期及身份提供方要求。仅有 webhook 接口并不意味着自动兼容任意 OAuth/OIDC 令牌。
令牌断言 API中的 errCode 为整数,可选的属性是对象,不是带类型的属性数组。asserterWebhookConfig.endpoint 是完整 URL,下面的示例使用 /v1/assert。
启动确定性的本地 mock
此 mock 只识别 githubtoken/github、googletoken/google、gitlabtoken/gitlab 三组输入,分别表示对应域的 user1。令牌与提供方不匹配时失败。它是测试 fixture,不验证真实令牌。 不会记录令牌值。仅绑定回环地址,生产环境必须替换为真实身份集成。
从仓库根目录在单独终端运行:
go run ./website/tests/examples/asserter// A deterministic mock for the token-assertion tutorial. Never use for real tokens.
package main
import (
"encoding/json"
"flag"
"log"
"net/http"
"strings"
"time"
)
type principal struct {
Type string `json:"type"`
Name string `json:"name"`
IDD string `json:"idd"`
}
type response struct {
Principals []principal `json:"principals,omitempty"`
ErrCode int `json:"errCode"`
ErrMessage string `json:"errMessage,omitempty"`
}
func assert(w http.ResponseWriter, r *http.Request) {
send := func(status int, value response) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(value)
}
if r.Method != http.MethodGet {
w.Header().Set("Allow", "GET")
send(405, response{ErrCode: 405, ErrMessage: "GET required"})
return
}
// Fixed fixtures model successful assertion. This is not signature validation.
domains := map[string]string{"githubtoken": "github", "googletoken": "google", "gitlabtoken": "gitlab"}
domain, ok := domains[r.Header.Get("x-token")]
if !ok || domain != r.Header.Get("x-idp") {
send(401, response{ErrCode: 401, ErrMessage: "invalid demonstration token or identity provider"})
return
}
if allowed := r.Header.Get("x-allowedIDD"); allowed != "" {
found := false
for _, name := range strings.Split(allowed, ",") {
found = found || strings.TrimSpace(name) == domain
}
if !found {
send(403, response{ErrCode: 403, ErrMessage: "identity domain is not allowed"})
return
}
}
send(200, response{Principals: []principal{{Type: "user", Name: "user1", IDD: domain}}})
}
func main() {
listen := flag.String("listen", "127.0.0.1:8080", "Local demonstration endpoint")
flag.Parse()
mux := http.NewServeMux()
mux.HandleFunc("/v1/assert", assert)
server := &http.Server{Addr: *listen, Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second}
log.Fatal(server.ListenAndServe())
}运行示例前
先按快速入门编译仓库根目录下的 ./bin/spctl、./bin/speedle-pms 和 ./bin/speedle-ads,需要 Go 1.26.10 或 Go 1.27.3(也可使用同一大版本的更新补丁版本)、Bash、curl 和 Python 3。下面各终端都从仓库根目录运行。为本页使用一个新的策略文件;仅在两个服务都停止后重建该文件。
初始化空策略文件:
printf '{}\n' > docs-policies.json终端一启动 PMS,并保持运行:
./bin/speedle-pms --store-type file --filestore-loc ./docs-policies.json --endpoint 127.0.0.1:6733 --grpc-endpoint 127.0.0.1:0 --insecure=true终端二启动 ADS,并保持运行:
./bin/speedle-ads --store-type file --filestore-loc ./docs-policies.json --endpoint 127.0.0.1:6734 --grpc-endpoint 127.0.0.1:0 --insecure=true --asserter-endpoint http://127.0.0.1:8080/v1/assert在第三个终端运行后续命令。PMS/ADS 共享同一个文件,策略更新通过 watch 异步到达 ADS;先查询确认已更新,再依赖新策略做决策。--insecure=true 仅用于回环地址上的本地示例,生产连接见安全配置。示例显式指定 PMS 地址,不修改 ~/.spctlconfig。完成后在两个服务终端按 Ctrl-C。
创建带身份域的测试策略
./bin/spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create service --json-file website/tests/examples/idd.json测试策略允许 GitHub 的 user1 读取、Google 的 user1 写入,所有域的 user1 租借。与身份域示例使用同一组规则。
验证令牌请求
依次预期为 Allow、Deny、Allow。第二条请求使用有效的 GitLab 身份,因此不能使用仅授予 GitHub 身份的读取权限。
curl --fail --silent --show-error -H 'Content-Type: application/json' \
-d '{"subject":{"token":"githubtoken","tokenType":"github"},"serviceName":"booksvc","resource":"book","action":"read"}' \
http://127.0.0.1:6734/authz-check/v1/is-allowedcurl --fail --silent --show-error -H 'Content-Type: application/json' \
-d '{"subject":{"token":"gitlabtoken","tokenType":"gitlab"},"serviceName":"booksvc","resource":"book","action":"read"}' \
http://127.0.0.1:6734/authz-check/v1/is-allowedcurl --fail --silent --show-error -H 'Content-Type: application/json' \
-d '{"subject":{"token":"gitlabtoken","tokenType":"gitlab"},"serviceName":"booksvc","resource":"book","action":"rent"}' \
http://127.0.0.1:6734/authz-check/v1/is-allowed无效令牌属于断言错误,不是正常的策略 Deny。当前 REST 实现仍可能返回 HTTP 200,同时包含 allowed=false 和非空的 errorMessage;必须同时检查响应体和 HTTP 状态,不能仅依据 reason 判断评估是否成功:
curl --silent --show-error -H 'Content-Type: application/json' \
-d '{"subject":{"token":"invalid-token","tokenType":"google"},"serviceName":"booksvc","resource":"book","action":"write"}' \
-w '\nHTTP %{http_code}\n' http://127.0.0.1:6734/authz-check/v1/is-allowed结束后分别在 mock、PMS、ADS 终端按 Ctrl-C。
对接真实提供方
可用完整 JSON 配置替代本地参数,其中断言部分如下:
{
"asserterWebhookConfig": {
"endpoint": "https://identity.example/v1/assert",
"clientCert": "/absolute/path/client.crt",
"clientKey": "/absolute/path/client.key",
"caCert": "/absolute/path/issuer-ca.crt",
"httpTimeout": 5
}
}这是服务器配置中的断言部分,部署时还要提供存储和服务端配置。caCert 验证 webhook 服务器,clientCert 与 clientKey 用于对方要求双向 TLS 的情况。按身份提供方文档通过 OpenID Connect 或 OAuth 获取令牌,并验证其实际支持的令牌类型。PMS/ADS 调用方仍需单独遵循安全配置。