从零开始的RPC(十一):统一认证与授权
理论部分
微服务安全的挑战和现状
在单体应用中,开发者可以通过简单的拦截器以及Session机制对用户的访间进行控制和记录。在目前微服务盛行的架构体系下,服务的数量在业务分解后急剧上升,每个微服务都需要对用户的行为进行认证和许可,明确当前访问用户的身份与权限级别。与此同时,整个系统可能还需对外提供一定的服务,比如第三方登录授权等。在这种情况下,如果要求每个微服务都实现各自的用户信息管理系统,既增加了开发的工作量,出错的概率也会增加。对此而言,统一的认证与授权就显得尤为必要和有效
目前主流的统一认证和授权方式有OAuth2、分布式Session和JWT等,其中又以OAuth2方案使用最为广泛,已经成为当前授权的行业标准
由于统一认证与授权方案将用户信息进行统一的管理和使用,这就很可能出现系统性能瓶颈的问题,甚至可能在认证和授权服务宕机会导致整个系统无法正常运行。与此同时,整合当前系统中各个服务的用户信息管理系统也存在一定的难度,在实践统一认证与授权方案时需要根据项目的现状理智选择方案
常见的认证与授权方案
行业授权标准 OAuth2.1
OAuth 2.1 是 OAuth 2.0 的安全增强版本,旨在解决 OAuth 2.0 中的安全问题并简化规范。它于 2021 年发布,是目前最新的行业授权标准。
OAuth 2.1 的核心角色
OAuth 2.1 定义了四个核心角色:
资源所有者(Resource Owner)
- 能够授予对受保护资源访问权限的实体
- 通常是最终用户(如登录的用户)
资源服务器(Resource Server)
- 托管受保护资源的服务器
- 能够根据访问令牌验证请求并提供资源
客户端(Client)
- 代表资源所有者请求访问受保护资源的应用程序
- 可以是网站、移动应用、桌面应用等
授权服务器(Authorization Server)
- 负责验证资源所有者身份并颁发访问令牌
- 管理授权过程和令牌生命周期
OAuth 2.1 协议流程
OAuth 2.1 的基本流程如下:
- 客户端请求授权:客户端向授权服务器请求授权
- 用户认证:资源所有者(用户)进行身份认证
- 用户授权:用户授权客户端访问其资源
- 颁发授权码:授权服务器向客户端颁发授权码
- 交换访问令牌:客户端使用授权码向授权服务器交换访问令牌
- 访问资源:客户端使用访问令牌向资源服务器请求受保护资源
- 验证令牌:资源服务器验证访问令牌的有效性
- 提供资源:验证通过后,资源服务器向客户端提供受保护资源
+--------+ +---------------+
| |--(A)- 请求授权 ------------------------->| |
| | | |
| |<-(B)- 认证用户 ------------------------- | |
| | | 授权服务器 |
| |<-(C)- 授权用户 ------------------------- | |
| 客户端 | | |
| |--(D)- 交换授权码获取访问令牌 ----------->| |
| | | |
| |<-(E)- 颁发访问令牌 ----------------------| |
+--------+ +---------------+
|
|
v
+-------------------------+
| |
|--(F)- 使用访问令牌请求资源 ->| 资源服务器
| |
|<-(G)- 验证令牌并提供资源 ----| |
+-------------------------+OAuth 2.1 客户端授权类型
OAuth 2.1 支持以下授权类型:
1. 授权码流程(Authorization Code Flow)
适用场景:Web 应用、移动应用、桌面应用等所有类型的客户端
特点:
- 最安全的授权方式
- 支持 PKCE(Proof Key for Code Exchange)增强安全性
- 适用于有后端的应用
流程:
- 客户端引导用户到授权服务器
- 用户登录并授权
- 授权服务器返回授权码
- 客户端使用授权码和客户端密钥交换访问令牌
2. 隐式授权流程(Implicit Flow)
注意:OAuth 2.1 已废弃此流程,建议使用授权码流程 + PKCE
3. 客户端凭证流程(Client Credentials Flow)
适用场景:服务器间通信、后台服务
特点:
- 不需要用户参与
- 直接使用客户端 ID 和密钥获取令牌
- 适用于机器间的认证
流程:
- 客户端使用客户端 ID 和密钥直接向授权服务器请求
- 授权服务器验证并返回访问令牌
4. 密码凭证流程(Password Flow)
注意:OAuth 2.1 已废弃此流程,建议使用授权码流程
5. 设备授权流程(Device Authorization Flow)
适用场景:无浏览器或输入受限的设备(如智能电视、智能家居设备)
流程:
- 设备显示用户代码和验证 URL
- 用户在其他设备上访问 URL 并输入代码
- 用户登录并授权
- 设备轮询授权服务器获取访问令牌
OAuth 2.1 的安全增强
OAuth 2.1 相比 OAuth 2.0 的主要安全增强:
- 强制使用 PKCE:所有授权码流程必须使用 PKCE,防止授权码拦截攻击
- 废弃不安全流程:移除了隐式授权和密码凭证流程
- 简化刷新令牌:统一了刷新令牌的处理方式
- 增强令牌安全:推荐使用短期访问令牌和刷新令牌
- 改进重定向 URI 验证:更严格的重定向 URI 验证规则
令牌类型
OAuth 2.1 定义了两种主要的令牌类型:
访问令牌(Access Token)
- 用于访问受保护资源
- 通常短期有效(如 15-60 分钟)
- 可以是 JWT 或不透明令牌
刷新令牌(Refresh Token)
- 用于获取新的访问令牌
- 长期有效(如几天到几个月)
- 存储在客户端安全的位置
OAuth 2.1 的最佳实践
- 始终使用 HTTPS:保护所有 OAuth 通信
- 使用 PKCE:增强授权码流程的安全性
- 安全存储客户端密钥:不要在客户端代码中硬编码
- 设置合理的令牌过期时间:访问令牌短期,刷新令牌长期
- 实现令牌撤销机制:允许用户撤销授权
- 使用范围(Scope):限制访问权限的范围
- 验证重定向 URI:确保只接受预注册的重定向 URI
常见的 OAuth 2.1 提供商
- Google:使用 OAuth 2.0(接近 2.1 标准)
- Facebook:使用 OAuth 2.0
- GitHub:使用 OAuth 2.0
- Microsoft Azure AD:支持 OAuth 2.0 和 OpenID Connect
- Auth0:支持 OAuth 2.1 标准
- Okta:支持 OAuth 2.1 标准
数据共享:分布式 Session
在 Web 服务中,Session 和 Cookie 是维护用户登录状态的传统方式。随着分布式系统和微服务架构的普及,单体应用中的 Session 管理方式已无法满足需求,分布式 Session 管理应运而生。
会话跟踪技术:Session 与 Cookie
会话(Session) 是指用户与应用交互的一系列连续操作,通常包含多个 HTTP 请求。由于 HTTP 协议的无状态特性,服务器无法在多次请求之间保持用户状态,因此需要会话跟踪技术来解决这个问题。
Session 和 Cookie 是最常用的会话跟踪机制:
Cookie:
- 存储在客户端浏览器中,是一小段文本信息
- 由服务器在响应中设置,客户端在后续请求中自动携带
- 通常包含会话标识(Session ID),用于服务器查找对应 Session
- 可设置过期时间、路径、域等属性
Session:
- 存储在服务器端,包含用户的状态信息
- 与客户端 Cookie 中的 Session ID 一一对应
- 可存储更复杂的用户数据,安全性更高
- 通常有服务器管理的过期时间
工作流程:
- 客户端首次请求服务器
- 服务器创建 Session,并生成唯一 Session ID
- 服务器将 Session ID 放入 Cookie 并返回给客户端
- 客户端后续请求自动携带该 Cookie
- 服务器根据 Cookie 中的 Session ID 查找对应的 Session 信息
- 服务器根据 Session 信息识别用户身份和状态
分布式 Session 的挑战
在单体应用中,Session 存储在单个服务器的内存中,管理简单。但在分布式架构中,面临以下挑战:
- Session 不一致:用户请求可能被负载均衡器分发到不同服务器
- Session 丢失:服务器重启或崩溃会导致 Session 丢失
- 扩展性差:Session 存储在服务器内存中限制了水平扩展能力
典型场景:
- 用户在服务器 A 登录,Session 存储在 A 上
- 后续请求被负载均衡到服务器 B
- 服务器 B 无法找到用户的 Session,导致用户需要重新登录
分布式 Session 的实现方案
| 实现方式 | Session 复制 | Session 粘滞 | 集中式管理 | 基于客户端 Cookie 管理 |
|---|---|---|---|---|
| 原理 | 多台服务器之间同步 Session 数据 | 通过负载均衡器将用户请求固定到特定服务器 | 使用独立存储服务(如 Redis)集中管理 Session | 将 Session 数据加密后存储在客户端 Cookie 中 |
| 优点 | 对开发透明,无需代码修改 | 实现简单,无额外存储开销 | 可靠性高,支持水平扩展 | 无依赖外部服务,部署简单 |
| 缺点 | 网络开销大,同步延迟,内存占用高 | 单点故障风险,负载不均 | 依赖外部存储服务,增加系统复杂度 | 安全性低,Cookie 大小限制,性能受影响 |
| 适用场景 | 服务器数量少,Session 数据量小 | 对高可用要求不高的场景 | 大型分布式系统,高可用要求 | 数据不敏感,用户体验要求高的场景 |
| 技术实现 | Tomcat Session 复制 | Nginx IP 哈希,会话粘性 | Redis、Memcached 存储 | 加密 Cookie,JWT |
最佳实践
推荐方案:集中式管理(Redis)
- 使用 Redis 作为 Session 存储,支持高并发和高可用
- 利用 Redis 的持久化和集群特性保证数据安全
- 提供统一的 Session 管理接口,简化开发
实现建议:
- 设置合理的 Session 过期时间(如 30 分钟)
- 对敏感数据进行加密存储
- 实现 Session 防篡改机制
- 考虑使用分布式锁处理并发更新
与其他方案对比:
- 分布式 Session vs JWT:JWT 无状态,适合前后端分离;Session 有状态,适合传统 Web 应用
- 分布式 Session vs OAuth2:OAuth2 更适合第三方授权场景,Session 更适合内部系统认证
技术选型考量
- 性能要求:Redis 等内存数据库性能优异,适合高并发场景
- 可靠性要求:多副本、持久化机制保证数据安全
- 运维复杂度:需要额外管理存储服务
- 成本考虑:增加了基础设施成本和维护成本
在实际项目中,应根据业务规模、性能要求和团队技术栈选择合适的分布式 Session 方案。对于大型微服务架构,集中式管理(Redis)通常是最优选择。
安全传输对象 JWT
JWT(JSON Web Token) 作为一个开放的标准,通过紧凑(快速传输,体积小)并且自包含(有效负载中将包含用户所需的所有的信息,避免了对数据库的多次查询)的方式,定义了用于在各方之间发送的安全JSON对象
JWT可以很好地充当OAuth2的访问令牌和刷新令牌的载体,这是Web双方之间进行安全传输信息的良好方式。当只有授权服务器持有签发和验证JWT的secret时,也就只有授权服务器能验证JWT的有效性以及签发带有签名的JWT,这就唯一保证了以JWT为载体的token的有效性和安全性
JWT一般表现形式如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30它由3部分组成,每部分通过.分隔开,分别是:
Header/头部
- typ:类型,其值一般为 JWT
- alg:加密算法,通常为 HMAC、SHA256或HS256

这部分会被Base64URL编码为JWT的头部:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Payload/载荷:载荷是JWT的第二部分,是用来携带有效信息的载体,主要是关于用户实体和附加元数据的声明,由以下三部分组成:
- Registered claims/注册声明:它是一组预定的声明,但并不强制要求使用。主要有iss(JWT签发者)、exp(JWT过期时间)、sub(JWT面向的用户)、aud(接受JWT的一方) 等属性信息
- Public claims/公开声明:在公开声明中可以添加任何信息,一般是用户信息或者业务扩展信息等
- Private claims/私有声明:它是被JWT提供者和消费者共同定义的声明,既不属于注册声明也不属于公开声明
一般不建议在Payload中添加任何的敏感信息,因为Base64是对称解密的,这意味着Payload中的信息的是可见的
这部分会被Base64 URL编码为JWT的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0Signature/签名
要创建签名,必须需要被编码后的头部、被编码后的有效负载以及一个secret,最后通过在头部定义的加密算法alg加密生成签名,生成签名的伪代码如下:HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret, )上述伪代码中使用的加密算法为HMACSHA256。Secret作为签发密钥,用于验证JWT以及签发JWT,所以只能由服务端持有,不该泄漏出去
实践部分
基于 JWT 实现的简单认证与授权系统
系统整体架构
认证与授权架构概述
本项目基于Kratos框架实现了一套完整的认证与授权系统,采用JWT(JSON Web Token)作为认证凭据,结合内存缓存实现了Token的状态管理。系统架构遵循DDD(领域驱动设计)分层结构,将认证与授权功能分散到不同的层次中,实现了职责分离和代码复用。
核心组件关系
- 数据层(data):负责Token的生成、存储和管理
- 服务层(service):实现业务逻辑,包括用户认证和授权
- 组件层(component):提供中间件和工具函数
- 服务器层(server):配置中间件链,处理HTTP和gRPC请求
授权服务器
用户服务
用户服务由UserService实现,提供用户注册、查询等功能。用户数据存储在SQLite数据库中,通过Ent ORM进行操作。用户模型包含ID、用户名、密码和权限等级等字段。
TokenGrant令牌生成器
TokenGrant功能由JWTRepo的NewJWT方法实现,负责为用户生成JWT令牌。生成过程包括:
- 创建包含用户信息和权限的
JWTClaim - 使用HS256算法签名生成JWT令牌
- 将令牌信息存储到内存缓存中
func (repo *JWTRepo) NewJWT(user *ent.User) (string, error) {
claim := NewJWTWithEntUser(user, repo.cfg.GetExpire().AsDuration())
token := jwtv5.NewWithClaims(jwtv5.SigningMethodHS256, claim)
tokenString, err := token.SignedString([]byte(repo.cfg.GetSecret()))
if err != nil {
return "", err
}
if err := repo.Set(claim.GetJTI(), claim, false); err != nil {
return "", err
}
return tokenString, nil
}TokenService令牌服务
TokenService由JWTRepo和JWTCache共同实现,提供以下功能:
- 令牌生成:为用户创建新的JWT令牌
- 令牌验证:验证令牌的有效性和状态
- 令牌刷新:更新令牌的过期时间
- 令牌吊销:从缓存中删除令牌,使其失效
TokenStore令牌存储器
TokenStore由JWTCache实现,使用内存map存储令牌信息。它包含:
- 缓存清理:定时清理过期令牌
- 并发安全:使用读写锁保证并发操作的安全性
- 优雅关闭:服务停止时正确清理资源
type JWTCache struct {
cfg *conf.Auth
mu sync.RWMutex
cache map[jti]*JWTClaim
cleanQueue chan jti // 缓存清理队列
interval time.Duration // 清理协程的扫描间隔
wg sync.WaitGroup
quit chan struct{}
}
func (J *JWTCache) Get(j jti) (*JWTClaim, error) {
J.mu.RLock()
defer J.mu.RUnlock()
if c, exists := J.cache[j]; exists {
return c, nil
} else {
return nil, ErrJWTNotFound
}
}
func (J *JWTCache) Set(j jti, claim *JWTClaim, overwrite bool) error {
if _, err := J.Get(j); err != nil {
// 若已存在凭据, 且不允许覆盖, 则返回异常: 凭据重复
if errors.Is(err, ErrDuplicateJWT) && !overwrite {
return ErrDuplicateJWT
// 若凭据不存在, 则可以直接存储
} else if !errors.Is(err, ErrJWTNotFound) {
log.Errorf("凭据存储失败: %v", err)
return err
}
}
J.mu.Lock()
J.cache[j] = claim
J.mu.Unlock()
return nil
}
func (J *JWTCache) Delete(j jti) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) // 删除操作有500ms期限
defer cancel()
select {
case J.cleanQueue <- j:
return nil
case <-ctx.Done():
if _, exists := J.cache[j]; exists {
return errors.InternalServer("凭据吊销失败", "服务器缓存繁忙")
} else {
return nil
}
}
}认证端点
系统提供了以下认证相关端点:
/api.v1.auth.Auth/Login:用户登录,获取JWT令牌/api.v1.auth.Auth/Logout:用户注销,吊销JWT令牌
请求访问令牌
用户通过登录接口获取访问令牌,登录流程:
- 用户提交用户名和密码
- 系统验证用户凭据
- 生成JWT令牌并返回给用户
- 令牌存储到内存缓存中
资源服务器
令牌认证
令牌认证通过两层中间件实现:
- JWT签名验证:使用Kratos内置的JWT中间件验证令牌签名
- Token状态验证:使用自定义的
ExtraJWTVerifier中间件验证令牌在缓存中的状态
// ExtraJWTVerifier
//
// 额外JWT状态校验器
func ExtraJWTVerifier(jr *data.JWTRepo) middleware.Middleware {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
if _, ok := transport.FromServerContext(ctx); ok {
// Do something on entering
var c *data.JWTClaim
var valid bool
rawClaim, _ := jwt.FromContext(ctx)
if c, valid = rawClaim.(*data.JWTClaim); !valid {
return nil, ErrJWTType
}
if !jr.Compare(c.GetJTI(), c) {
return nil, ErrJWTMismatch
}
defer func() {
// Do something on exiting
}()
}
return handler(ctx, req)
}
}
}鉴权
鉴权功能在服务层实现,通过JWTClaim提供的权限判断方法进行权限检查:
IsUserOrHigher():检查用户是否具有用户或更高权限IsAdminOrHigher():检查用户是否具有管理员或更高权限UnableDoAnyThing():检查用户是否为访客权限
type JWTClaim struct {
UserID int
Username string
Privilege int32 // 对应grpc的(整型)枚举常量
jwtv5.RegisteredClaims
}
func (J *JWTClaim) IsUserOrHigher() bool {
return J.Privilege >= privilegeUser
}
func (J *JWTClaim) IsAdminOrHigher() bool {
return J.Privilege >= privilegeAdmin
}
func (J *JWTClaim) UnableDoAnyThing() bool {
return J.Privilege <= privilegeGuest
}访问受限资源
系统中的受限资源包括:
- 用户信息查询:需要用户或更高权限
- 用户列表查询:需要用户或更高权限
- 注销操作:需要有效的JWT令牌
请求链路分析
登录请求链路
- HTTP请求:客户端向
/api.v1.auth.Auth/Login发送POST请求 - 中间件处理:请求经过白名单中间件,登录接口被排除在JWT验证之外
- 服务处理:
AuthService.Login方法验证用户凭据 - 令牌生成:调用
JWTRepo.NewJWT生成JWT令牌 - 令牌存储:令牌信息存储到
JWTCache中 - 响应返回:返回包含令牌的登录响应
func (s *AuthService) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginReply, error) {
switch {
case req.GetUsername() == "":
return nil, ErrEmptyUsername
case req.GetPassword() == "":
return nil, ErrEmptyPassword
}
userInDB, err := s.ur.GetUserByUsername(ctx, req.GetUsername())
if err != nil {
if errors.As(err, errors.Error{}) { // 如果是Kratos自定义异常, 那么可以直接返回
return nil, err
}
return nil, ErrUserNotExist
}
switch {
case req.GetUsername() != userInDB.Username:
return nil, ErrUserNotExist
case req.GetPassword() != string(userInDB.Password):
return nil, ErrPasswordMismatch
}
var token string
if token, err = s.jr.NewJWT(userInDB); err != nil {
s.log.Errorf("无法为用户[%v]生成JWT凭据: %v", userInDB.Username, err)
return nil, ErrJWTGenerateFailed
}
return &pb.LoginReply{
Message: "登录成功",
Token: token,
}, nil
}访问受限资源请求链路
- HTTP请求:客户端携带JWT令牌向受限资源发送请求
- JWT验证:请求经过JWT中间件,验证令牌签名
- 状态验证:请求经过
ExtraJWTVerifier中间件,验证令牌状态 - 权限检查:服务层检查用户权限
- 业务处理:处理业务逻辑
- 响应返回:返回业务响应
注
- Error路径下由于
go error到protobuf error的序列化问题,请求耗时会比正常耗时要大
注销请求链路
- HTTP请求:客户端携带JWT令牌向
/api.v1.auth.Auth/Logout发送请求 - JWT验证:请求经过JWT中间件,验证令牌签名
- 状态验证:请求经过
ExtraJWTVerifier中间件,验证令牌状态 - 服务处理:
AuthService.Logout方法从缓存中吊销令牌 - 响应返回:返回注销成功响应
func (s *AuthService) Logout(ctx context.Context, req *pb.Empty) (*pb.LogoutReply, error) {
claim, ok := jwt.FromContext(ctx)
if !ok || claim == nil {
return nil, ErrJWTMissing
}
var c *data.JWTClaim
if c, ok = claim.(*data.JWTClaim); !ok {
return nil, ErrJWTType
}
if err := s.jr.Revoke(c.GetJTI()); err != nil {
s.log.Errorf("无法注销用户[%v]的JWT凭据: %v", c.GetUsername(), err)
return nil, ErrLogoutFailed
}
return &pb.LogoutReply{
Message: "成功退出登录",
}, nil
}安全特性
令牌管理
- 令牌吊销:支持通过
JWTRepo.Revoke方法吊销令牌 - 过期清理:定时清理过期令牌,防止内存泄漏
- 状态验证:通过内存缓存验证令牌状态,实现Token内省
权限控制
- 权限等级:实现了Guest、User、Admin、SuperAdmin四级权限
- 权限判断:提供了丰富的权限判断方法
- 细粒度控制:在服务层实现细粒度的权限控制
安全防护
- 密码验证:登录时验证用户密码
- 令牌签名:使用HS256算法对令牌进行签名
- 并发安全:使用读写锁保证缓存操作的安全性
- 资源清理:服务停止时正确清理资源
代码结构
核心文件
internal/data/jwt.go:实现JWT令牌的生成、存储和管理internal/service/auth.go:实现认证相关的业务逻辑internal/component/custom_jwt_server.go:实现JWT验证中间件internal/server/http.go:配置HTTP服务器和中间件
HTTP服务器配置示例:
func NewHTTPServer(bc *conf.Bootstrap,
// repository
jr *data.JWTRepo,
greeter *service.GreeterService, userSvc *service.UserService, authSvc *service.AuthService,
logger log.Logger) *http.Server {
if err := component.InitTracer(bc); err != nil {
log.Errorf("init tracer error: %v", err)
}
var c = bc.Server
var opts = []http.ServerOption{
http.Middleware(
recovery.Recovery(),
logging.Server(logger),
tracing.Server(),
metadata.Server(),
component.Whitelistify(selector.Server(jwt.Server(
func(token *jwtv5.Token) (any, error) {
return []byte(bc.Auth.GetSecret()), nil
}, jwt.WithSigningMethod(jwtv5.SigningMethodHS256),
jwt.WithClaims(func() jwtv5.Claims {
// 这里可以使用自定义的claim, 这样等下在FromContext里拿出来可以直接尝试断言
return &data.JWTClaim{}
}),
))),
// 额外JWT状态校验
component.Whitelistify(selector.Server(component.ExtraJWTVerifier(jr))),
),
}
// 服务器配置...
srv := http.NewServer(opts...)
// 注册服务...
return srv
}关键接口
JWTClaimInterface:定义JWT声明的接口,包含权限判断方法JWTCacheInterface:定义Token缓存的接口JWTRepo:封装JWT相关的操作
技术特点
优点
- 架构清晰:分层架构,职责分离明确
- 功能完整:实现了完整的认证与授权功能
- 性能优秀:使用内存缓存存储Token,访问速度快
- 安全可靠:多重验证机制,保证系统安全
- 可扩展性:模块化设计,易于扩展
待改进之处
- 双Token模式:目前只实现了单Token模式,可考虑添加Refresh Token
- 分布式部署:内存缓存不适合分布式部署,可考虑使用Redis
- 权限管理:可添加更细粒度的权限控制
- 监控告警:可添加Token使用情况的监控
总结
本项目实现了一套完整的认证与授权系统,基于JWT和内存缓存,提供了用户认证、令牌管理、权限控制等功能。系统架构清晰,代码质量高,功能完整,是一个优秀的认证与授权实现范例。通过合理的分层设计和模块化结构,系统具有良好的可维护性和可扩展性,能够满足大多数应用场景的需求。