Files
ops-mcp/internal/middleware/auth.go
T
yangzhaohan fb2c3a6cae Go MCP Server 项目初始化:工具注册架构 + 编译运行通过
- Tool 接口 + Registry 注册中心,工具模块化插拔
- system 工具:health / info
- database 工具:db_query / db_tables / db_table_info / db_explain(四层 SQL 安全)
- docker 工具:docker_ps / docker_logs / docker_inspect(白名单 + 大小限制)
- middleware:auth(JWT+APIKey)/ ratelimit(令牌桶)/ audit(slog 结构化)
- stdio / SSE 双传输模式,Viper 多环境配置
- Dockerfile 多阶段构建(golang:alpine → scratch),~15MB 镜像
- docker-compose.yml 一键部署 + 安全加固(read_only / no-new-privileges / cap_drop)
- go build ./... / go vet ./... / go test ./... 全部通过

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-06 10:26:52 +08:00

95 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/golang-jwt/jwt/v5"
)
type contextKey string
const (
KeySubject contextKey = "subject"
KeyScopes contextKey = "scopes"
)
// Auth 验证 Bearer TokenAPI Key 或 JWT)。
type Auth struct {
apiKeys map[string]string // key → description
jwtSecret []byte
}
func NewAuth(apiKeys map[string]string, jwtSecret string) *Auth {
return &Auth{
apiKeys: apiKeys,
jwtSecret: []byte(jwtSecret),
}
}
// Middleware 从 HTTP Header 中提取并验证 Token。
func (a *Auth) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := extractBearer(r)
if token == "" {
http.Error(w, "missing Bearer token", http.StatusUnauthorized)
return
}
// 尝试 API Key 验证
if desc, ok := a.apiKeys[token]; ok {
ctx := context.WithValue(r.Context(), KeySubject, fmt.Sprintf("apikey:%s", desc))
ctx = context.WithValue(ctx, KeyScopes, []string{"*"})
next.ServeHTTP(w, r.WithContext(ctx))
return
}
// 尝试 JWT 验证
if a.jwtSecret != nil {
claims, err := a.parseJWT(token)
if err == nil {
ctx := context.WithValue(r.Context(), KeySubject, claims.Subject)
ctx = context.WithValue(ctx, KeyScopes, claims.Scopes)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
}
http.Error(w, "invalid token", http.StatusUnauthorized)
})
}
type customClaims struct {
jwt.RegisteredClaims
Scopes []string `json:"scopes"`
}
func (a *Auth) parseJWT(tokenStr string) (*customClaims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &customClaims{},
func(t *jwt.Token) (any, error) {
return a.jwtSecret, nil
},
)
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*customClaims)
if !ok || !token.Valid {
return nil, fmt.Errorf("invalid claims")
}
return claims, nil
}
func extractBearer(r *http.Request) string {
auth := r.Header.Get("Authorization")
if auth == "" {
return ""
}
if !strings.HasPrefix(auth, "Bearer ") {
return ""
}
return strings.TrimPrefix(auth, "Bearer ")
}