使用ZITADEL保护Go API的完整指南
前言
在现代应用开发中,API安全是至关重要的环节。本文将详细介绍如何使用ZITADEL身份认证平台来保护Go语言编写的API服务。通过OAuth 2.0 Token Introspection机制,我们可以为API端点添加精细的访问控制。
准备工作
环境要求
在开始之前,请确保您已经具备以下条件:
- 一个可用的ZITADEL实例
- Go 1.16或更高版本
- 基本的Go语言开发环境
ZITADEL控制台配置
首先需要在ZITADEL控制台完成以下配置步骤:
-
创建应用
- 在控制台首页点击"创建应用"
- 选择项目并选择"Other"作为框架类型
- 填写应用名称并选择"API"作为应用类型
-
配置认证方式
- 选择JWT作为认证方法
- 创建新的JSON密钥并设置合适的过期时间
- 务必下载并保存密钥文件
-
创建服务用户
- 在用户管理界面切换到"服务用户"标签
- 创建新用户并设置名称和用户名
- 选择Bearer作为访问令牌类型
-
生成个人访问令牌(PAT)
- 在用户管理的左侧面板选择"个人访问令牌"
- 设置过期时间并生成令牌
- 安全保存生成的令牌,后续开发将使用它
Go项目设置
添加依赖
首先需要将ZITADEL Go SDK添加到项目中:
go get -u github.com/zitadel/zitadel-go/v3
API示例代码解析
下面是一个完整的API示例,包含三个端点:
-
健康检查端点 (
/api/healthz
)- 公开访问,无需认证
- 始终返回"OK"响应
-
任务列表端点 (
/api/tasks
)- 需要有效访问令牌
- 返回当前任务列表
-
添加任务端点 (
/api/add-task
)- 需要具有"admin"角色的访问令牌
- 允许添加新任务到列表
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"sync"
"github.com/zitadel/zitadel-go/v3/pkg/authorization"
"github.com/zitadel/zitadel-go/v3/pkg/authorization/oauth"
"github.com/zitadel/zitadel-go/v3/pkg/http/middleware"
"github.com/zitadel/zitadel-go/v3/pkg/zitadel"
)
var (
domain = flag.String("domain", "", "Your ZITADEL instance domain")
key = flag.String("key", "", "Path to the key.json")
port = flag.String("port", "8089", "Port to run the server on")
)
func main() {
flag.Parse()
// 初始化ZITADEL客户端
client, err := zitadel.New(*domain, *key, zitadel.WithJWTProfile())
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
// 创建授权中间件
auth, err := authorization.New(client,
authorization.WithOPA(authorization.DefaultOPAConfig),
)
if err != nil {
log.Fatalf("failed to create auth: %v", err)
}
// 创建路由
router := http.NewServeMux()
// 健康检查端点
router.HandleFunc("/api/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `"OK"`)
})
// 任务存储
var (
tasks = make(map[string]struct{})
tasksLock sync.Mutex
)
// 任务列表端点
router.Handle("/api/tasks", auth.RequireAuthorization(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tasksLock.Lock()
defer tasksLock.Unlock()
// 检查用户角色
ctx := r.Context()
claims, ok := middleware.ClaimsFromContext(ctx)
if !ok {
http.Error(w, "claims not found", http.StatusInternalServerError)
return
}
// 如果是管理员,添加特殊任务
result := make(map[string][]string)
result["tasks"] = make([]string, 0, len(tasks)+1)
for task := range tasks {
result["tasks"] = append(result["tasks"], task)
}
if claims.Roles["admin"] {
result["tasks"] = append(result["tasks"], "create a new task on /api/add-task")
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}),
oauth.Introspection(),
))
// 添加任务端点
router.Handle("/api/add-task", auth.RequireAuthorization(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
task := r.FormValue("task")
if task == "" {
http.Error(w, "task is required", http.StatusBadRequest)
return
}
tasksLock.Lock()
defer tasksLock.Unlock()
tasks[task] = struct{}{}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `"task '%s' added"`, task)
}),
oauth.Introspection(),
authorization.RequireRole("admin"),
))
// 启动服务器
log.Printf("INFO server listening, press ctrl+c to stop addr=http://localhost:%s", *port)
if err := http.ListenAndServe(":"+*port, router); err != nil {
log.Fatalf("failed to start server: %v", err)
}
}
运行和测试API
启动服务
使用以下命令启动API服务:
go run main.go --domain your-domain.zitadel.cloud --key ./api.json
成功启动后,您将看到日志输出:
INFO server listening, press ctrl+c to stop addr=http://localhost:8089
测试端点
- 测试健康检查端点
curl -i http://localhost:8089/api/healthz
预期响应:
HTTP/1.1 200 OK
"OK"
- 测试任务列表端点(无令牌)
curl -i http://localhost:8089/api/tasks
预期响应:
HTTP/1.1 401 Unauthorized
unauthorized: authorization header is empty
- 测试任务列表端点(带有效令牌)
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/tasks
预期响应(初始为空列表):
HTTP/1.1 200 OK
{}
- 测试添加任务端点(无admin角色)
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/add-task --data "task=My new task"
预期响应:
HTTP/1.1 403 Forbidden
permission denied: missing required role: `admin`
配置admin角色
- 在ZITADEL控制台中,导航到您的项目
- 选择"Roles"并创建名为"admin"的新角色
- 在"Authorization"部分,将该角色授予您的服务用户
再次测试
- 添加任务(带admin角色)
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/add-task --data "task=My new task"
预期响应:
HTTP/1.1 200 OK
"task 'My new task' added"
- 查看更新后的任务列表
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/tasks
预期响应(注意自动添加的特殊任务):
HTTP/1.1 200 OK
{"tasks":["My new task","create a new task on /api/add-task"]}
最佳实践和安全建议
-
密钥管理
- 永远不要将密钥文件提交到版本控制系统
- 考虑使用密钥管理服务来存储和访问密钥
-
令牌处理
- 设置合理的令牌过期时间
- 实现令牌刷新机制
-
角色设计
- 遵循最小权限原则
- 创建细粒度的角色定义
-
错误处理
- 避免在错误响应中泄露敏感信息
- 记录安全相关事件
通过本指南,您已经学会了如何使用ZITADEL为Go API添加强大的身份认证和授权功能。这种集成方式不仅安全可靠,而且可以轻松扩展到更复杂的权限场景。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考