引言

对于IAM系统,它的主要功能的设计会体现在设计文档和项目描述中,但依然存在一些小的技术细节,可能无关乎项目的主要功能和目标,但是我仍然觉得十分有意义,为了不让它们被遗失或遗忘,我决定单独用一篇博客把它们汇总起来

DTO设计-控制面认证使用Access Token和Refresh Token双Token

我先前对于账户登录控制鲜有思考,具体的实现也都是一笔带过,并不考虑安全性,而是直接完成登录认证的核心功能。

而这个双Token的设计则是实现了安全性的飞跃的同时兼顾了运行效率

我们来一步步引入双Token机制

不用token会怎么样

我们就需要在前端登录后,由前端自己保存用户的账号密码,每次发送带权限的请求都需要附加账户密码字段,由服务器验证权限,这就导致了账户密码频繁通过互联网传输,曝光度过高,同时服务器也需要反复查询账户密码用于验证,降低效率。

小结:

  • 安全性:差,因为敏感信息曝光度过高,泄漏后造成的影响也很大
  • 效率: 差,因为需要反复查询用户账户数据

使用一个toekn会怎样

使用Token机制后,直接解决了账户密码的曝光度过高问题和服务器需要反复查询的问题,因为Token相对较短,可以缓存在内存中查询。

但是Token会面临安全性和性能考量:

  • 过期时间过长:一旦泄漏就会是长时间的权限泄漏
  • 过期时间过短: 用户需要频繁重新登录

使用两个Token用于改进

为此我们:

  • 权限功能交给Access Token管理
  • 过期时间功能交给Refresh Token管理

这样就能 一定程度上解决单Token的问题,它们两个token的职责如下:

AccessToken 的角色

  • 高频使用
  • 每次 API 请求都带
  • 权限校验靠它

典型特征

属性
有效期 很短(10–30 分钟)
使用场景 所有受保护 API
泄露影响 有限
存储位置 内存 / Header
1
Authorization: Bearer <access_token>

RefreshToken 的角色

  • 低频使用
  • 只在 AccessToken 过期时用
  • 换新的 AccessToken

典型特征

属性
有效期 长(7–30 天)
使用场景 /auth/refresh
泄露影响 可控(可吊销)
存储位置 更安全(HttpOnly Cookie / 安全存储)

职责分层:Router层,Handler层和DTO层

Error抽象:防止底层Error击穿分层抽象

当我们将后端服务分层抽象成RepositoryModelControllerService等层后,为了解耦各层的实现,以及为了防止底层的具体实现暴露给上层,我们都要对各层的接口进行抽象封装。然而特别容易疏漏的,就是对Error的抽象封装。因为Go里的error基本都是派生自errors包的,不会有语法问题,但是不代表它没有设计问题。

就比如Repository层选择先抽象接口,隐藏数据持久化的具体实现,使MysQL、MongoDB等持久化均可用于Repository,但是如果直接返回gorm错误,就会导致底层的具体实现通过错误类型暴露,所以要封装Repository层的错误,把gorm错误翻译成Repository层的错误。

Repository层设计-何时加Context

由于初次接触这种比较精细的后端层次划分,光在初期考虑设计Repository的语义和实现就比较困难,加入Context属于工程实现,因此被滞后加入了。

1
2
3
4
5
6
7
8
9
10
第二阶段:边界稳定后,再补 ctx(你现在正好到这一步)
现在你已经:
Repository 抽象稳定
MySQL 实现写完
Service 层开始出现
Handler 也基本成型
这时再加 ctx:
不影响架构理解
能统一全链路
是“自然演进”,不是“被迫设计”

go项目的model解析问题

在一次执行 go mod tidy 时遇到如下报错:

1
module xxx/internal/iam-api-server found, but does not contain package xxx/internal/iam-api-server/v1/model

一开始误以为是 Go 的 internal 包可见性限制导致的,但实际上与 internal 机制无关internal 只限制跨模块访问,而当前项目是在同一个 module(github.com/sis-shen/sup-iam)内,按规则是可以正常引用的。

真正原因是 module 结构被破坏了:

项目中曾经通过 replaceinternal/iam-api-server 映射为一个独立 module,或者该目录下残留了 go.mod 文件,导致 Go 在解析依赖时,把它当成一个子 module处理。而这个子 module 并没有包含 v1/model 这个包路径,于是报错 “does not contain package”。

更隐蔽的一点是:
在同一项目中,部分代码可以正常 import,而另一部分却报错,这是因为 Go 在不同包编译时,可能走了不同的 module 解析路径(缓存 + replace + 子 module 混用),从而出现不一致行为。


解决方法

  1. 确保整个项目只使用一个 module(推荐)

    • 删除 replace 配置
    • 删除 internal/iam-api-server 下的 go.mod
  2. 保证 import 路径与目录结构完全一致

  3. 清理缓存并重新解析依赖

1
2
go clean -modcache
go mod tidy

一句话总结

这类问题本质不是代码错误,而是 “把 internal 目录误当成独立 module 使用,导致 Go 依赖解析路径错乱”