项目复盘:登录注册与接口联动
作者:admin
复盘这个博客里登录注册真正打通的关键链路:验证码、JWT、/me、前端本地存储、自动续期,以及页面和接口怎么联动起来。
文章图库(点击可放大)
项目复盘:登录注册与接口联动
这篇不是讲“怎么从零写一个认证系统”,而是复盘这个博客里最关键的一条链路:注册、登录、获取当前用户、前端保存状态、带 token 请求接口、过期后自动续期,最后把页面和接口真正串起来。
很多项目一开始登录页能写出来,但真正卡住的往往不是表单,而是下面这些细节:
- 注册接口通了,但登录后页面不知道你是谁
- 能拿到 token,但刷新页面后状态丢了
- 前端能显示登录成功,但发留言、发文章还是 401
- access token 过期后,必须手动退出再登录
- 后端有用户表,但前端拿不到头像、昵称这些资料
我这个项目复盘的重点,就是把这些“前后端真正联动”的细节讲透。
如果你也在做 Next.js + Django REST Framework 这种前后端分离项目,尤其正在处理登录注册、JWT、个人资料、权限接口,这篇会比较对口。
这次真正打通的是什么
不是单独做了一个登录页,而是打通了下面这条完整链路:
- 前端先拉验证码
- 注册或登录提交时,一起带上验证码
- 后端校验验证码
- 登录成功后返回
access + refresh - 前端把 token 写进
localStorage - 前端立刻请求
/api/auth/me/ - 后端返回当前用户资料,包括
username / email / is_staff / avatar_url / nickname - 前端把当前用户资料缓存起来,Navbar、个人中心、留言厅、发文页都基于这份状态工作
- 后续受保护接口统一带
Authorization: Bearer <access> - access 过期后,前端自动用 refresh 去换新 token,再自动重试原请求
这条链路通了,登录注册才不再只是“表单成功提交”。
后端这边是怎么拆的
1. 验证码接口单独提供
后端给了一个公开接口:
GET /api/auth/captcha/
它返回两样东西:
captcha_keycaptcha_image_url
前端加载登录页和注册页时,先请求一次这个接口,把图片显示出来,并把 captcha_key 暂存起来。
这个设计的好处是清晰:
- 图形验证码由后端生成
- 前端只负责显示和回填
- 登录、注册都复用同一套验证码逻辑
2. 注册接口不是裸写用户,而是先过序列化器
注册接口走的是:
POST /api/auth/register/
注册序列化器做了两件关键事:
- 校验
captcha_key + captcha_value - 校验通过后再
create_user
项目里注册用的字段是:
{
"username": "your_name",
"email": "you@example.com",
"password": "12345678",
"captcha_key": "xxx",
"captcha_value": "abcd"
}
这里一个重要细节是:验证码验证成功后会立即删除。
也就是说,这个验证码是一次性的,防止重复利用。
3. 登录不是直接用默认 JWT,而是扩展了验证码校验
登录接口走的是:
POST /api/auth/token/
底层用的是 rest_framework_simplejwt,但不是直接拿默认 TokenObtainPairView 就完事了,而是自己扩展了一个带验证码的序列化器。
也就是说,登录不只是用户名密码:
{
"username": "your_name",
"password": "12345678",
"captcha_key": "xxx",
"captcha_value": "abcd"
}
后端顺序是:
- 先校验验证码
- 验证通过后销毁验证码
- 再走 JWT 登录逻辑
- 返回
access和refresh
这样做的意义很直接:
把验证码挡在账号密码校验前面,减少撞库和脚本爆破风险。
4. /auth/me/ 是前后端联动的关键接口
如果只有登录接口返回 token,没有 /me,前端其实还是不知道“当前这个 token 对应的是谁”。
这个项目里专门做了:
GET /api/auth/me/
这个接口必须登录后访问,返回的是当前用户资料。
而且返回的不是一个干巴巴的用户名,而是直接序列化 Profile:
usernameemailis_staffavatar_urlnicknamesignaturebio
这一步很关键,因为前端顶部头像、个人中心、留言头像、管理员权限判断,都是围绕这份数据展开的。
5. 用户 Profile 用信号自动补
这个项目里用户资料不是散在用户表里的,而是单独有一个 Profile 模型。
为了避免“注册后没有 profile,前端 /me 报错”,后端还加了信号:
@receiver(post_save, sender=User)
def create_or_update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
return
Profile.objects.get_or_create(user=instance)
这段逻辑很朴素,但很有用:
- 新用户创建时自动补 profile
- 老用户更新时兜底补齐 profile
这样前端拿 /me 时,不用担心 profile 缺失。
前端这边真正做了什么
1. token 没放 cookie,而是放 localStorage
这个项目前端做法是:
anime_blog_accessanime_blog_refreshanime_blog_user
都存在 localStorage 里。
对应的工具函数大致是:
export function setTokens(access: string, refresh?: string) {
localStorage.setItem("anime_blog_access", access);
if (refresh) {
localStorage.setItem("anime_blog_refresh", refresh);
}
}
这样做的优点是简单直接:
- 前端页面跳转时读写方便
- 调试时直观
- 对这种个人博客项目足够实用
缺点也很明确:
- 需要自己处理 XSS 风险
- 不是最严格的安全方案
但从这个项目的阶段来看,这个取舍是合理的,重点是先把整条链路跑通。
2. 登录成功后不是直接跳首页,而是先补当前用户信息
登录页的关键流程不是“拿到 token 就结束”,而是:
const data = await login(username, password, captchaKey, captchaValue);
setTokens(data.access, data.refresh);
const me = await fetchMe(data.access);
setCurrentUser(me);
router.push("/");
这一步非常重要。
因为如果你只保存 token,不立刻请求 /me:
- Navbar 头像拿不到
- 是否管理员不知道
- 留言、发文这种页面没法立刻切到已登录状态
换句话说,token 是“凭证”,`/me` 才是“当前用户状态”。
3. 注册成功后没有自动登录,而是回到登录页
注册页流程相对保守:
await register(username, email, password, captchaKey, captchaValue);
router.push("/login");
这意味着当前实现是:
- 注册成功
- 跳转到登录页
- 用户再手动登录一次
这不是最省步骤的做法,但逻辑很清楚,也方便后期在注册后增加:
- 邮箱验证
- 审核开关
- 注册成功提示页
对当前项目来说,这个选择是稳的。
接口联动真正难的地方,不是“能请求”,而是“状态一致”
很多前后端分离项目一开始最容易忽略的是:
登录状态不是只在登录页用一次,而是会影响整个站点。
这个项目里,登录状态实际上联动了这些功能:
- Navbar 用户区域显示昵称和头像
- 个人中心
/profile - 留言厅发留言 / 回留言
- 文章评论
- 点赞
- 管理员发文章
/write - 后台管理
/dashboard - 相册上传和管理
所以真正要解决的是:
前端任何一个需要登录的请求,都得拿到可用 token。
为什么后来还补了“自动续期”
这个项目前面最实际的一个坑就是:
access token 过期后,很多操作会直接 401,导致每次改完代码或者测试久一点,就得重新退出、重新登录。
这个体验很差,也会影响调试。
后面补的方案是:
- 所有需要登录的请求走统一封装
- 请求先带 access token
- 如果返回 401
- 前端自动请求:
POST /api/auth/token/refresh/
- 拿新 access token
- 自动重试刚才失败的请求
这套逻辑做完之后,像留言、评论、发文、资料更新这些功能就顺多了。
大致结构是:
if (res.status === 401) {
const refreshedAccessToken = await tryRefreshAccessToken();
// 拿到新 token 后自动重试
}
这一步是“能用”和“好用”的分水岭。
这个项目里,自动续期解决了哪些具体问题
补完自动续期以后,下面这些接口都不再需要你频繁重登:
- 留言发布
- 留言回复
- 文章评论
- 点赞
- 个人资料读取和更新
- 相册上传、编辑、删除
- 仪表盘请求
- 发文章
- 正文图片上传
也就是说,前端后面不是各页面自己瞎处理 401,而是统一通过 API 层兜住了。
这比把刷新 token 的逻辑散在每个页面里强很多。
这次复盘下来,真正起作用的设计点有哪些
1. 验证码前置
登录、注册都先过验证码,而不是只做表单校验。
2. token 和用户资料分开处理
- token 负责鉴权
/me负责当前用户展示状态
这两个职责分开以后,前端逻辑会清楚很多。
3. Profile 独立建模
头像、昵称、签名这些扩展信息没有硬塞进用户基础表,而是放在 Profile 里,更利于后面继续扩展。
4. 登录态不只服务登录页,而是服务整站
一旦涉及留言、评论、发文、后台,登录状态就必须是“全站状态”。
5. 统一封装带 token 请求
把 token 续期、401 重试放在 api.ts,而不是散落在各个页面组件里。
这套实现还不算完美,后面还能继续补什么
虽然现在这套已经能稳定用,但如果以后要继续打磨,还可以往下做:
1. 把 token 存储策略再升级
现在是 localStorage。
如果以后更强调安全性,可以考虑:
- HttpOnly Cookie
- CSRF 配套处理
- 更严格的登录会话策略
2. 给登录态做全局上下文
现在主要是本地存储 + 页面初始化读取。
后面可以进一步抽成:
- React Context
- Zustand
- 或者统一 auth provider
这样页面状态更新会更自然。
3. 增加更细的错误提示
比如把下面几类错误拆开:
- 验证码过期
- 验证码错误
- 用户名已存在
- 密码太短
- refresh token 失效
这样用户体验会更完整。
4. 补登录后重定向
比如访问 /write 被打回登录页后,登录成功自动跳回原页面,而不是统一回首页。
这会更像成熟后台系统。
这次项目复盘我最明确的结论
登录注册这件事,真正难的从来不是“写两个表单”。
真正难的是把下面这些环节接起来:
- 验证码
- JWT
- 当前用户资料
- 前端本地状态
- 权限接口
- token 过期续期
- 页面显示联动
把它们串起来以后,登录注册才不是一个孤立功能,而是整个站点的基础设施。
如果只做到了“能登录”,那还只是第一步。
做到“登录后整站行为一致、token 过期还能平滑续期”,前后端联动这部分才算真的跑通。
这篇复盘对应到这个项目的几个关键接口
GET /api/auth/captcha/
POST /api/auth/register/
POST /api/auth/token/
POST /api/auth/token/refresh/
GET /api/auth/me/
PATCH /api/auth/profile/
如果你也在做类似博客、后台管理或者社区型站点,这几个接口基本就是登录注册链路里最核心的一组。