文档目录
glen
· 预计阅读 3 分钟

第 2 章 登录与 JWT:令牌怎么来、怎么用

本文导航
第 2 章 登录与 JWT:令牌怎么来、怎么用

第 2 章 登录与 JWT:令牌怎么来、怎么用

权限校验的第一步是识别「你是谁」。owl-admin 用用户名密码登录,签发 JWT,后续请求在 Authorization 头里带上令牌。

登录接口

POST /api/v1/users/login
Content-Type: application/json
{
  "username": "admin",
  "password": "123qwe"
}

成功响应:

{
  "success": true,
  "msg": "操作成功",
  "data": {
    "id": "19941996",
    "username": "glen",
    "nickname": "超级管理员",
    "roles": [{ "name": "superAdmin" }],
    "isSuperAdmin": true,
    "accessToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

data 里既有用户资料,也有 accessToken。

两种登录主体

1. 配置超管

用户名等于 conf/app.yaml 的 admin.username 时,不查数据库,直接构造内置超管:

admin:
  username: admin
  password: "$2a$10$..."   # bcrypt hash
  • 超管 ID 固定为 19941996;
  • 拥有 *:*:*;
  • IsSuperAdmin: true;
  • 不受 Casbin 限制。

密码必须是 bcrypt hash。可以在「系统工具 / 密码工具」里计算密文;如果把入口切换为 ConsoleShell,也可以执行:

go run . admin:gen-pwd 你的明文密码

2. 数据库用户

普通用户从 admin_user 表读取,按用户名查询,并 Preload("Roles")。密码用 bcrypt 校验,失败返回登录错误。

令牌里有什么

type UserClaims struct {
	model.User
	jwt.RegisteredClaims
}

也就是说,JWT 的 payload 里包含:

  • 用户基础字段(ID、用户名、昵称、头像、状态等);
  • 角色列表;
  • isSuperAdmin;
  • 标准声明:exp、iss。

密码字段的 JSON 标签是 json:"-",不会进入令牌。

令牌由 conf/jwt.yaml 配置:

signing-key: "your-secret-key"
issuer: "owl-admin"
expire: 3600

expire 单位是秒。

携带令牌

Authorization: Bearer <accessToken>

中间件会去掉 Bearer 前缀再解析:

token := ctx.Request.Header.Get("Authorization")
user, err := jwtService.ParseToken(strings.Replace(token, "Bearer ", "", -1))

解析成功后,用户信息会被放进:

  • ctx.Get("user"):完整的 *model.User;
  • 请求上下文:user_id、username、nickname、roles。

db.BaseModel 的 BeforeCreate / BeforeUpdate 会从上下文读 user_id、username,自动填充创建人、修改人。

登录后的三个接口

GET /api/v1/users/me             当前用户资料
GET /api/v1/users/me/menus       当前用户菜单
GET /api/v1/users/me/permissions 当前用户权限标识
curl "http://127.0.0.1:8080/api/v1/users/me" \
  -H "Authorization: Bearer <token>"

curl "http://127.0.0.1:8080/api/v1/users/me/menus?app=admin" \
  -H "Authorization: Bearer <token>"

curl "http://127.0.0.1:8080/api/v1/users/me/permissions" \
  -H "Authorization: Bearer <token>"
  • 超管:菜单返回全部(不含按钮),权限返回 ["*:*:*"];
  • 普通用户:菜单来自角色已分配的菜单,权限来自这些菜单上的 DependentsPermission。

/users/me/menus 支持 app 参数,多个子系统用逗号分隔:

GET /api/v1/users/me/menus?app=admin,cms

令牌过期与角色变更

  • 令牌过期后,中间件解析失败,返回 401,需要重新登录;
  • 修改用户角色后,旧令牌里的 roles 不会立刻变化,但接口鉴权用的是数据库里的 Casbin 策略,所以权限会按新角色生效;
  • 前端缓存的权限列表可能仍旧,见第 6 章。

登录接口的保护

登录路由挂了两层保护:

r.Use(middleware.RateLimiter(time.Second*1, 2)).
  Use(middleware2.LoginCaptchaGuard(userSvc, trustedDeviceRepo, captchaSvc)).
  Post("/users/login", router.AccessPublic, userHandle.Login)
  • 限流:每秒 2 次;
  • 验证码:按配置和可信设备判断是否需要图形验证码;
  • 登录成功会发布 UserLoginSuccess 事件,写入登录日志。

请求体里的 deviceId 用于记录可信设备,captcha 用于提交验证码答案。

常见问题

登录返回「用户名或密码错误」

  • 配置超管:检查 conf/app.yaml 里的 bcrypt hash 是否对应明文密码;
  • 数据库用户:检查用户是否被禁用、密码是否被重置。

带令牌仍返回 401

  • 请求头是否写成 Authorization: Bearer xxx;
  • 令牌是否过期;
  • signing-key 是否在重启后变了(多实例部署必须一致)。

超管能访问,普通用户 403

这是正常现象,说明鉴权在工作。去检查普通用户的角色、角色菜单和 Casbin 策略,见第 5、6 章。

自检

  • [ ] 能说清配置超管和数据库用户的区别;
  • [ ] 知道 JWT 里包含哪些字段;
  • [ ] 会用三个 me 接口检查当前登录态;
  • [ ] 能定位 401 与 403 的不同原因。

评论

加载评论中…

上一篇 第 1 章 权限模型总览:RBAC 与 Casbin 下一篇 第 3 章 访问级别:四种开关决定接口怎么校验

BitLabs © 2026 · 论坛讨论 · 博客长文 · 文档手册