随便写点技术性的文章
从单体应用迁移到微服务架构,虽能收获松耦合、可扩展等诸多优势,但也引入了新的安全挑战。微服务通过开放 API 实现服务间通信,与单体应用相比:
因此,微服务架构需要一套与单体应用截然不同的安全解决方案,核心聚焦于认证(Authentication) 与鉴权(Authorization) 两大核心能力。

| 架构模式 | 策略类型 | 核心特点 |
|---|---|---|
| 无 API 网关 | 1.1 服务各自认证鉴权 | 耦合度高、重复开发、维护成本高 |
| 1.2 独立认证授权服务 | 认证鉴权集中化,但未解决 API 层统一拦截问题 | |
| 有 API 网关 | 2.1 网关认证 + 服务鉴权 | 认证统一化,鉴权分散导致权限管理混乱 |
| 2.2 网关统一认证鉴权 | 性能高效,但网关压力过大,灵活性不足 | |
| 2.3 独立认证服务 + 网关鉴权 | 认证与鉴权解耦,兼顾性能与灵活性(推荐) |
推荐策略:2.3 独立认证服务 + 网关鉴权
选择该策略的核心原因:
服务端识别客户端身份的两种核心模式,其本质区别在于是否存储客户端状态信息:
结合 Kratos 框架特性,最终技术选型如下:
JSON Web Token(JWT)是基于 RFC 7519 标准的轻量级令牌,用于在分布式系统中安全传递用户身份信息。其结构由三部分组成:
Casbin 是一个开源访问控制框架,核心优势在于 将权限模型与业务逻辑解耦,支持 ACL、RBAC、ABAC 等多种权限模型,提供跨语言支持与灵活的规则配置。
{主体(subject), 资源(object), 操作(action)})keyMatch匹配/foo/*路径)Kratos 通过中间件机制实现认证与鉴权的统一拦截,以下是完整实现步骤:
用于封装 JWT 令牌信息与 Casbin 鉴权所需的核心数据(主体、资源、操作):
const (
ClaimAuthorityId = "authorityId" // JWT负载中存储权限ID的字段
)
// SecurityUser 实现认证鉴权所需的用户信息接口
type SecurityUser struct {
Path string // 访问的资源路径(对应API操作名)
Method string // 请求方法(*表示所有方法)
AuthorityId string // 用户权限ID(如admin/moderator)
}
// NewSecurityUser 创建SecurityUser实例
func NewSecurityUser() authzM.SecurityUser {
return &SecurityUser{}
}
// ParseFromContext 从上下文解析JWT信息与请求信息
func (su *SecurityUser) ParseFromContext(ctx context.Context) error {
// 从上下文获取JWT负载
if claims, ok := jwt.FromContext(ctx); ok {
su.AuthorityId = claims.(jwtV4.MapClaims)[ClaimAuthorityId].(string)
} else {
return errors.New("jwt claim missing")
}
// 从上下文获取请求操作信息
if header, ok := transport.FromServerContext(ctx); ok {
su.Path = header.Operation() // Kratos中API操作名(如/admin.v1.AdminService/Login)
su.Method = "*"
} else {
return errors.New("request header missing")
}
return nil
}
// 以下方法实现authzM.SecurityUser接口,提供Casbin所需的三元组信息
func (su *SecurityUser) GetSubject() string { return su.AuthorityId } // 主体(用户权限ID)
func (su *SecurityUser) GetObject() string { return su.Path } // 资源(API操作名)
func (su *SecurityUser) GetAction() string { return su.Method } // 操作(请求方法)
// NewWhiteListMatcher 定义无需认证鉴权的API操作名
func NewWhiteListMatcher() selector.MatchFunc {
whiteList := map[string]bool{
"/admin.v1.AdminService/Login": true, // 登录接口免认证
}
// 返回匹配函数:白名单内的操作返回false(跳过中间件)
return func(ctx context.Context, operation string) bool {
return !whiteList[operation]
}
}
// NewAuthMiddleware 创建JWT认证中间件
func NewAuthMiddleware(logger log.Logger, apiKey string) http.ServerOption {
return http.Middleware(
recovery.Recovery(), // 异常恢复中间件
tracing.Server(), // 链路追踪中间件
logging.Server(logger), // 日志中间件
// 选择性应用JWT中间件(白名单接口跳过)
selector.Server(
jwt.Server(
// JWT密钥验证函数
func(token *jwtV4.Token) (interface{}, error) {
return []byte(apiKey), nil
},
jwt.WithSigningMethod(jwtV4.SigningMethodHS256), // 指定HS256加密算法
),
).Match(NewWhiteListMatcher()).Build(),
)
}
// 服务启动选项配置
var opts = []http.ServerOption{
NewAuthMiddleware(logger, ac.ApiKey), // 注册JWT认证中间件
// CORS跨域配置
http.Filter(handlers.CORS(
handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"}),
handlers.AllowedOrigins([]string{"*"}),
)),
}
前端需在 HTTP 请求头中携带 JWT 令牌,格式为Bearer {Token}:
// 生成认证请求头
export default function authHeader() {
const userStr = localStorage.getItem("user");
let user = userStr ? JSON.parse(userStr) : null;
if (user && user.token) {
return { Authorization: `Bearer ${user.token}` };
} else {
return {};
}
}
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && r.act == p.act
p, ROLE_ADMIN, /admin.v1.AdminService/*, *
p, ROLE_MODERATOR, /admin.v1.AdminService/ListUsers, *
g, admin, ROLE_ADMIN
g, moderator, ROLE_MODERATOR
// NewAuthzMiddleware 创建Casbin鉴权中间件
func NewAuthzMiddleware(logger log.Logger) http.ServerOption {
// 从文件加载Casbin模型与策略
model, _ := model.NewModelFromFile("../../configs/authz/authz_model.conf")
adapter := fileAdapter.NewAdapter("../../configs/authz/authz_policy.csv")
return http.Middleware(
recovery.Recovery(),
tracing.Server(),
logging.Server(logger),
// 选择性应用鉴权中间件(白名单接口跳过)
selector.Server(
casbinM.Server(
casbinM.WithCasbinModel(model), // 注入Casbin模型
casbinM.WithCasbinPolicy(adapter), // 注入权限策略
casbinM.WithSecurityUserCreator(NewSecurityUser), // 注入用户信息构造器
),
).Match(NewWhiteListMatcher()).Build(),
)
}
在 HTTP 服务选项中添加 Casbin 中间件(需在 JWT 中间件之后):
var opts = []http.ServerOption{
NewAuthMiddleware(logger, ac.ApiKey), // 先认证
NewAuthzMiddleware(logger), // 后鉴权
// ...其他中间件
}
func (s *AdminService) Login(_ context.Context, req *v1.LoginReq) (*v1.User, error) {
// 1. 验证用户名密码(实际场景需查询数据库)
if req.UserName == "" || req.Password == "" {
return nil, errors.New("invalid username or password")
}
// 2. 分配用户角色
var roles []string
switch req.UserName {
case "admin":
roles = append(roles, "ROLE_ADMIN")
case "moderator":
roles = append(roles, "ROLE_MODERATOR")
default:
return nil, errors.New("user not found")
}
// 3. 生成JWT令牌
securityUser := &SecurityUser{AuthorityId: req.UserName}
token := securityUser.CreateAccessJwtToken([]byte(s.apiKey))
// 4. 返回用户信息与令牌
id := uint64(10)
email := "hello@kratos.com"
return &v1.User{
Id: &id,
UserName: &req.UserName,
Token: &token,
Email: &email,
Roles: roles,
}, nil
}
前端提交用户名密码 → 登录接口验证身份 → 生成 JWT 令牌 → 返回给前端 → 前端存储令牌(localStorage)
| 技术组件 | 核心作用 |
|---|---|
| Golang | 后端开发语言 |
| React | 前端开发框架 |
| Kratos | 微服务框架(提供中间件、服务治理能力) |
| Consul | 服务发现与配置中心 |
| Jaeger | 分布式链路追踪 |
| JWT | 无状态身份认证令牌 |
| Casbin | 通用权限控制框架 |