DToken-Go认证与权限管理使用指南
1、DToken-Go 能解决什么问题
在后台管理系统、用户中心或开放平台中,完成账号密码校验之后,通常还需要签发 Token、保存登录状态、校验接口权限,以及处理退出登录、多设备登录和账号封禁等问题。
DToken-Go 将这些能力组织为可以按需接入的 Go 模块,提供登录认证、Session、角色与权限、终端管理,以及 Refresh Token、SSO、OAuth2 等扩展能力。
认证用于确认当前是谁,授权用于判断当前账号能做什么。 两者都需要服务端执行,不能只靠前端隐藏按钮或菜单。
本文按项目文档及本地源码副本(提交 91e4df7)整理基础用法,主要介绍默认核心包和 GoFrame 集成。实际接入时应以所选版本的源码与 go.mod 为准;本文没有为本站安装依赖或启动示例服务。
2、模块结构与安装方式
2.1、按用途选择模块
该仓库使用多模块结构,不是所有代码都位于同一个 Go Module 中。
| 模块 | 用途 |
|---|---|
defaults |
组装默认存储、编码器、Token 生成器等组件 |
dtoken |
对外提供登录、退出、权限等通用 API |
core |
配置、Manager、组件接口及核心实现 |
integrations/gf |
GoFrame 上下文、中间件与 API 导出 |
integrations/gin |
Gin 框架集成 |
com/storage/redis |
使用 Redis 保存认证相关状态 |
sso |
按需接入的单点登录模块 |
独立使用认证核心时,可以安装:
1 | go get github.com/Zany2/dtoken-go/defaults |
已经使用 GoFrame 的项目,可以直接选择对应集成包:
1 | go get github.com/Zany2/dtoken-go/integrations/gf |
需要 Redis 存储时,再引入:
1 | go get github.com/Zany2/dtoken-go/com/storage/redis |
以上命令用于说明模块路径。正式项目应固定经过验证的兼容版本,并提交 go.mod、go.sum。仓库中的 go.work 用于仓库自身的多模块开发,业务项目不需要直接复制它。项目安装说明。
2.2、不要混用不同层级的 API
初次使用可以先选定一种方式:普通 Go 程序使用 defaults 和 dtoken,GoFrame 应用使用别名为 gfdt 的框架集成包。
全局函数、实例门面和请求上下文 API 的参数形式不完全相同。例如全局的 dtoken.Logout(ctx, token) 需要显式 Token,而请求上下文中的 dCtx.Auth().Logout(ctx) 可以从当前请求解析 Token。
3、一个最小的登录与退出示例
默认 Builder 使用内存存储,适合先理解调用流程。下面是一个控制台示例,不包含 HTTP 登录接口,也不执行账号密码校验。
1 | package main |
示例依次完成初始化、注册 Manager、登录、查询身份、添加演示权限和退出。AutoRenew(false) 用于先观察固定 TTL;需要活跃续期时再按业务要求开启。
内存存储跟随进程存在,进程重启后不能指望保留登录态,多实例之间也不会自动共享这些数据。登录认证指南。
4、真实的登录接口应该怎样组织
Login(ctx, loginID) 的含义是为一个已经确认的身份建立登录态。它不会自动查询你的用户表,也不会验证用户填写的密码。
真实流程可以组织为:
1 | 接收用户名与密码 |
不要把浏览器提交的 user_id 直接交给 Login,也不要照搬演示程序,为所有登录用户赋予 admin 角色。用户 ID、角色与权限都应来自服务端可信的业务数据。
设备类型可以使用 web、app 等业务约定;设备 ID 用于区分具体终端。客户端提供的设备标识属于管理辅助信息,不应单独作为身份认证依据。
当配置 TokenName("Authorization") 并开启 Header 读取时,客户端可以按以下形式携带登录凭证:
1 | GET /api/me |
这里的域名和路由仅说明请求结构,不代表本站已经提供该认证接口。Token 不放入 URL,不写入普通访问日志;服务端接收来源应与客户端发送方式保持一致。
5、有效期、活跃超时和续期
| 配置 | 含义 | 示例值 |
|---|---|---|
Timeout |
本次签发或续期后的 Token 有效时长 | 7200,两小时 |
ActiveTimeout |
允许的最长不活跃时长 | 1800,半小时 |
AutoRenew |
登录校验时是否按规则触发自动续期 | true 或 false |
RenewMaxRefresh |
剩余 TTL 达到该阈值时才考虑续期 | 3600,一小时 |
RenewInterval |
同一个 Token 的最小自动续期间隔 | 60,一分钟 |
RefreshTokenTimeout |
刷新凭证的有效时长 | 2592000,三十天 |
Builder 中这些数值时间配置的单位是秒,不要直接把 time.Hour 的纳秒数强制转换后传入。对支持 Duration 的配置,也可以使用 TimeoutDuration(2 * time.Hour) 等明确时长的入口。
开启自动续期后,Timeout 不能理解为“从首次登录起最多只能在线两小时”。持续活跃并成功续期的登录态可能继续存在;若业务需要强制重新认证,应另行设计最长登录时长策略。
配置之间还有关系:启用自动续期时不能把 Timeout 设成永不过期;续期阈值不能大于有效期,最小续期间隔也应小于有效期及已启用的活跃超时。初始化时检查 Build() 的错误,能及早发现不合法组合。配置指南。
6、角色与权限怎么使用
6.1、区分角色与权限
角色可以表达人员分组,例如 editor、admin;权限可以表达具体动作,例如 article:read、article:publish。
给账号添加角色,并不意味着框架会自动从业务数据库解析这个角色拥有的所有权限。角色到权限的映射需要由业务系统提供,或通过动态权限来源接入。
下面的函数适合在可信的服务端授权流程中调用,演示给已有登录会话添加角色和权限:
1 | func GrantEditorAccess(ctx context.Context, loginID string) error { |
这是函数片段,依赖 context 与 dtoken 导入。正式角色分配还应写入业务权限表,并处理权限变更后的缓存或 Session 同步;不要把演示授权函数直接暴露为任意用户可调用的接口。
6.2、接口中先检查登录,再检查权限
已经持有 Token 的服务端代码,可以使用:
1 | func CheckArticlePublish(ctx context.Context, token string) error { |
Has* 适合只需要布尔值的判断;接口层希望保留拒绝或存储异常的具体原因时,可以使用返回错误的 Check*。检查某个权限不等于检查某条数据的归属,编辑文章时仍需判断作者、租户等业务条件。
多个权限还可以表达 AND、OR 关系,分别表示全部满足或至少满足一个。权限字符串支持分段通配符,例如 article:*;命名时保持分隔符与层级一致,不要把一个宽泛通配符当成所有接口都需要的默认权限。权限管理指南。
7、权限从数据库读取:AccessProvider
初步接入时可以把角色、权限放在 Session 中;如果业务已经有用户、角色和权限表,或者权限需要频繁调整,可以使用 AccessProvider 从业务系统解析。
Provider 能获得账号、认证体系和设备等上下文,业务可据此查询数据库或权限服务。使用时需要明确以下区别:
| Provider 返回值 | 含义 |
|---|---|
nil, nil |
本次不接管,允许继续使用 Session 权限数据 |
| 空切片、无错误 | 明确表示没有权限,不回退到 Session |
| 非空权限列表 | 使用 Provider 返回的数据 |
| 错误 | 本次权限解析失败,应拒绝并按接口规则处理 |
尤其在权限撤销时,不能把“查到零条权限”误写成 nil, nil,否则可能继续使用旧的 Session 授权。动态查询也要设计超时、缓存失效和故障告警。AccessProvider 指南。
8、接入 Redis 存储
多实例部署时,应让相关实例使用一致的认证配置和共享存储。下面演示在初始化阶段从环境变量读取 Redis URL,创建存储并注册 Manager:
1 | package authsetup |
例如本地无密码 Redis 的 URL 可以是 redis://127.0.0.1:6379/0。真实密码从环境配置读取,不写进文章或版本库;连接错误的详细内容留在受控日志中,不直接返回客户端。
KeyPrefix 用于区分项目或环境,生产部署应使用生产环境自己的前缀。Manager 初始化一次后供请求复用,不要在每个请求里创建 Redis 连接和 Manager。
上面的函数采用默认组件所有权,初始化成功后由应用关闭 Manager 时释放其持有的组件。多个 Manager 共用同一存储客户端时,需要按项目的组件所有权选项安排统一关闭;先停止新请求并排空正在执行的业务,再关闭认证组件。Redis 存储指南、组件生命周期说明。
9、在 GoFrame 中接入
9.1、先初始化框架 Manager
在 GoFrame 项目启动时,可以使用 gfdt.NewBuilder() 构建 Manager,再调用 gfdt.SetManager(mgr)。Builder 的返回值仍需检查错误;Redis 存储也可以通过 SetStorage 注入。
基础顺序如下:
1 | 加载配置 → 创建存储和 Manager → 注册 Manager |
应用退出时,在 HTTP 请求排空后关闭 Manager。不要在普通控制器内调用全局清理函数。
9.2、路由与中间件示例
下面是路由注册函数。它依赖启动阶段已经注册的默认 Manager,并接收业务已经实现的登录处理器、文章处理器和认证错误处理器;它不是完整的启动程序。
1 | package authroute |
上下文中间件用于接入请求上下文,不能把“已经注册上下文”理解为“所有接口已经要求登录”。AuthMiddleware 才在受保护路由上校验登录态,权限中间件继续检查对应业务权限。
authFail 应由业务统一实现:未登录或 Token 失效通常返回 HTTP 401,权限不足或账号受限返回 403,存储与服务内部故障返回适当的 5xx,详细错误写入服务端日志。返回后终止本次处理,避免继续执行业务。示例中的三个处理器都应传入有效函数。
项目还有动态路由授权入口 AccessMiddleware,可根据匹配的业务路由声明权限。接入初期先把固定路由的登录与权限关系理清,再集中管理规则,避免误把后台接口设置为公开接口。完整 GoFrame 示例、GoFrame 中间件实现。
10、刷新令牌与自动续期的区别
自动续期主要围绕当前仍有效的登录凭证延长 TTL;Refresh Token 则用于通过单独的刷新凭证换取新的令牌对。需要双令牌时,可以在身份校验成功后调用 LoginWithRefreshToken。
下面的函数片段展示调用顺序,假设 Manager 已初始化,loginID 来自可信的身份校验结果:
1 | func RefreshExample(ctx context.Context, loginID string) error { |
刷新会轮换凭证,客户端应保存新的令牌对,不继续使用旧的 Refresh Token。多个请求同时发现需要刷新时,应合并成一次刷新流程,避免并发消费同一旧凭证。
刷新接口不能被“必须持有有效 Access Token”的中间件挡住,否则访问令牌到期后反而无法刷新;它应单独校验 Refresh Token,并配合自己的限流与异常处理。当前项目的普通登录刷新与 OAuth2 刷新是不同入口,不要混用。Refresh Token 指南。
11、终端管理与多认证体系
11.1、退出、踢下线与顶下线
| 操作 | 主要用途 |
|---|---|
Logout |
当前凭证正常退出 |
Kickout |
管理端主动踢出,保留下线原因 |
Replace |
登录替换场景,保留被顶下线原因 |
LogoutByLoginID |
对指定账号的全部终端执行退出 |
选择操作时也要选择范围:当前 Token、账号、设备类型或某个设备。退出当前浏览器时,不应误调用账号全部终端退出。
IsConcurrent、IsShare、MaxLoginCount 等配置共同决定并发登录和 Token 复用策略。需要多个独立会话时,不要同时期待所有登录都共享同一个 Token;具体组合参考Session 与终端管理。
11.2、用户端与管理端隔离
同一个应用同时提供用户端和后台管理端时,可以通过 AuthType("user")、AuthType("admin") 注册不同的 Manager。
AuthType 参与认证数据的命名空间隔离,KeyPrefix 更适合区分项目或环境,两者职责不同。注册不同认证体系之后,登录、查询、退出和路由中间件都应选择对应体系。
尤其注意全局 Login 的可选参数顺序是设备类型、设备 ID、认证体系。Login(ctx, id, "web") 中的 web 是设备类型,不是认证体系;需要指定体系时应按完整参数位置传入,或使用项目提供的实例、选项式入口。多认证体系指南。
12、常见问题与接入建议
| 问题 | 排查方向 |
|---|---|
| 登录后调用接口仍然未登录 | 检查 Manager 注册、Token 来源、认证体系和请求头 |
| 本地正常,多实例部署后时好时坏 | 检查是否仍使用各进程独立的内存存储 |
添加了 admin 角色仍无文章权限 |
角色与具体权限的映射需要业务提供 |
| 修改数据库权限后接口权限未变化 | 检查 Session 权限、Provider 返回值与缓存失效策略 |
| 刷新一次成功,再用原凭证刷新失败 | 刷新凭证已轮换,客户端应保存最新令牌对 |
| Builder 初始化失败 | 检查时间单位、续期约束和组件配置,不忽略错误 |
| JWT 能解析但登录校验失败 | 仍需检查服务端登录态、封禁、会话与有效期 |
项目的 JWT 模式并不意味着完全无状态:Session、终端和权限等能力仍会使用服务端存储。需要 JWT 时应按对应指南配置自己的密钥,不能把客户端能够解析 Token 当成服务端认证通过。JWT 指南。
建议先跑通“身份校验 → 登录 → 受保护接口 → 退出”的基本流程,再接入数据库权限、Redis、多终端和双令牌。Nonce、Ticket、ShortKey、OAuth2 等可选能力按实际场景启用,SSO 则作为独立模块设计接入边界。
本文示例已按项目接口整理,用于说明使用方式;没有运行认证服务、连接 Redis 或进行端到端验证。完整行为和版本变化可继续查阅项目文档中心及示例目录。



文章评论
分享你的观点,也欢迎提出问题。
写下你的想法
所有评论
正在加载评论…