<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>后端技术 on 洛克里亚的五度 博客</title>
    <link>https://lv-blog.pages.dev/tags/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/</link>
    <description>Recent content in 后端技术 on 洛克里亚的五度 博客</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Fri, 03 Apr 2026 11:45:26 +0800</lastBuildDate>
    <atom:link href="https://lv-blog.pages.dev/tags/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>构建一个聊天室需要的基础知识</title>
      <link>https://lv-blog.pages.dev/posts/programming/backend/fundamentals-for-building-a-chatroom/</link>
      <pubDate>Fri, 03 Apr 2026 11:45:26 +0800</pubDate>
      <guid>https://lv-blog.pages.dev/posts/programming/backend/fundamentals-for-building-a-chatroom/</guid>
      <description>&lt;h1 id=&#34;基础知识&#34;&gt;基础知识&lt;/h1&gt;
&lt;h2 id=&#34;tcp-三次握手&#34;&gt;TCP 三次握手&lt;/h2&gt;
&lt;p&gt;客户端 - 服务器&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三次握手目的&lt;/strong&gt;：客户端的收发没问题，服务器的收发也没问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;第一次握手客户端发-syn同步请求&#34;&gt;第一次握手：客户端发 SYN（同步请求）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;客户端&lt;/strong&gt;：我啥也不知道，我发个 &lt;strong&gt;SYN&lt;/strong&gt; 消息出去，看看有没有人接收到。&lt;/li&gt;
&lt;li&gt;如果没人回复，我再发几次（超时重传）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这个时刻，客户端知道的事实是&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;我发出去了，但不知道有没有人收到 ❌&lt;/li&gt;
&lt;li&gt;我能不能收消息？不知道 ❌&lt;/li&gt;
&lt;li&gt;对方存不存在？不知道 ❌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&#34;第二次握手服务器回-synack同步确认&#34;&gt;第二次握手：服务器回 SYN+ACK（同步+确认）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;服务器&lt;/strong&gt;收到了客户端的 SYN。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务器回复 &lt;strong&gt;SYN+ACK&lt;/strong&gt;：“我收到你的消息了，我也发一条试试，看看你能不能收到。”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;这个时刻，服务器知道的事实是&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端发消息是没有问题的 ✅（因为我收到了）&lt;/li&gt;
&lt;li&gt;服务器收消息是没有问题的 ✅（因为我收到了）&lt;/li&gt;
&lt;li&gt;服务器不知道服务器发消息有没有问题 ❌（我发出去了，但不知道对方收到没）&lt;/li&gt;
&lt;li&gt;服务器不知道客户端收消息有没有问题 ❌（我发出去了，但不知道对方收到没）&lt;/li&gt;
&lt;li&gt;服务器需要发送一条 SYN+ACK，等对方回复 ACK 来确认“我能发、对方能收”&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;当客户端收到 SYN+ACK 后，客户端可以确定以下事实&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我发消息是没有问题的 ✅（我发的 SYN 被收到了）&lt;/li&gt;
&lt;li&gt;我收消息也是没有问题的 ✅（我收到了对方的回复）&lt;/li&gt;
&lt;li&gt;服务器收消息是没有问题的 ✅（对方收到了我的 SYN）&lt;/li&gt;
&lt;li&gt;服务器发消息也是没有问题的 ✅（我收到了对方的 SYN+ACK）&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;客户端这边已经确认全双工通信没问题了！&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;但是服务器他还有顾虑（不知道自己能不能发、不知道我能不能收），我再发个 ACK 给他吧&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&#34;第三次握手客户端发-ack确认&#34;&gt;第三次握手：客户端发 ACK（确认）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;客户端&lt;/strong&gt;收到 SYN+ACK 后，回复一个 &lt;strong&gt;ACK&lt;/strong&gt;：“我收到你的 SYN+ACK 了，我这边收发都没问题，你也可以放心了。”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;当服务器收到这个 ACK 后，服务器可以确定以下事实&lt;/strong&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>为什么有时候后端需要维护一张Refresh_Tokens表</title>
      <link>https://lv-blog.pages.dev/posts/programming/backend/why-backend-maintains-a-refresh-tokens-table/</link>
      <pubDate>Mon, 02 Mar 2026 20:07:07 +0800</pubDate>
      <guid>https://lv-blog.pages.dev/posts/programming/backend/why-backend-maintains-a-refresh-tokens-table/</guid>
      <description>&lt;h1 id=&#34;正常-jwt-请求流程&#34;&gt;正常 JWT 请求流程&lt;/h1&gt;
&lt;pre class=&#34;mermaid&#34;&gt;sequenceDiagram
    participant User as 用户
    participant Frontend as 前端（浏览器/App）
    participant Auth as 认证服务器
    participant Backend as 后端API服务器

    Note over User,Backend: 1. 登录阶段
    User-&gt;&gt;Frontend: 输入用户名/密码
    Frontend-&gt;&gt;Auth: POST /login (凭证)
    Auth-&gt;&gt;Auth: 验证凭证
    Auth--&gt;&gt;Frontend: 返回 access_token + refresh_token
    Frontend-&gt;&gt;Frontend: 存储token（内存/localStorage）
    Frontend--&gt;&gt;User: 登录成功

    Note over User,Backend: 2. 正常请求阶段
    User-&gt;&gt;Frontend: 请求受保护资源
    Frontend-&gt;&gt;Backend: GET /api/resource&lt;br/&gt;Authorization: Bearer access_token
    Backend-&gt;&gt;Backend: 验证access_token签名和过期时间
    Backend--&gt;&gt;Frontend: 返回请求的资源
    Frontend--&gt;&gt;User: 展示数据

    Note over User,Backend: 3. Access Token过期
    User-&gt;&gt;Frontend: 继续请求
    Frontend-&gt;&gt;Backend: GET /api/resource&lt;br/&gt;Authorization: Bearer access_token(已过期)
    Backend--&gt;&gt;Frontend: 401 Unauthorized (token过期)
    Frontend-&gt;&gt;Frontend: 检测到401，触发刷新逻辑

    Note over Frontend,Auth: 4. 刷新Token阶段
    Frontend-&gt;&gt;Auth: POST /refresh&lt;br/&gt;refresh_token
    Auth-&gt;&gt;Auth: 验证refresh_token
    Auth--&gt;&gt;Frontend: 返回新的 access_token（可选的新refresh_token）
    Frontend-&gt;&gt;Frontend: 更新存储的access_token
    Frontend-&gt;&gt;Backend: 重试原请求（新access_token）
    Backend--&gt;&gt;Frontend: 返回请求的资源
    Frontend--&gt;&gt;User: 展示数据
&lt;/pre&gt;
&lt;h1 id=&#34;为什么不能只靠jwt&#34;&gt;为什么不能只靠JWT&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;refresh token 发出去后后端不保存，会变成 &lt;strong&gt;不可控的长期通行证&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;无法主动踢人下线&lt;/li&gt;
&lt;li&gt;无法注销单点设备&lt;/li&gt;
&lt;li&gt;无法判断 token 是否被盗用（攻击者拿到了 refresh token 可以一直刷新 access token）&lt;/li&gt;
&lt;li&gt;无法实现会话管理&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;使用-refresh-tokens-表后的流程图&#34;&gt;使用 refresh tokens 表后的流程图&lt;/h1&gt;
&lt;pre class=&#34;mermaid&#34;&gt;sequenceDiagram
    participant User as 用户
    participant Frontend as 前端
    participant Auth as 认证服务器
    participant DB as 数据库(refresh_tokens表)
    participant Backend as 后端API服务器

    Note over User,DB: 1. 登录阶段（签发token + 入库）
    User-&gt;&gt;Frontend: 输入用户名/密码
    Frontend-&gt;&gt;Auth: POST /login
    Auth-&gt;&gt;Auth: 验证凭证
    Auth-&gt;&gt;DB: 生成唯一refresh_token_id&lt;br/&gt;INSERT INTO refresh_tokens&lt;br/&gt;(user_id, token_hash, expires_at,&lt;br/&gt; device_info, ip_address, revoked)
    DB--&gt;&gt;Auth: 插入成功
    Auth-&gt;&gt;Auth: 生成access_token + refresh_token&lt;br/&gt;(refresh_token包含id引用)
    Auth--&gt;&gt;Frontend: 返回access_token + refresh_token
    Frontend-&gt;&gt;Frontend: 存储token
    Frontend--&gt;&gt;User: 登录成功

    Note over User,Backend: 2. 正常请求（与之前相同）
    User-&gt;&gt;Frontend: 请求资源
    Frontend-&gt;&gt;Backend: GET /api/resource&lt;br/&gt;Authorization: Bearer access_token
    Backend-&gt;&gt;Backend: 验证access_token
    Backend--&gt;&gt;Frontend: 返回资源

    Note over Frontend,DB: 3. Access Token过期 → 刷新
    Frontend-&gt;&gt;Backend: GET /api/resource (access_token过期)
    Backend--&gt;&gt;Frontend: 401 Unauthorized
    Frontend-&gt;&gt;Auth: POST /refresh&lt;br/&gt;refresh_token

    Note over Auth,DB: 4. 刷新验证（多步校验）
    Auth-&gt;&gt;Auth: 解析refresh_token，提取token_id
    Auth-&gt;&gt;DB: SELECT * FROM refresh_tokens&lt;br/&gt;WHERE id = token_id
    DB--&gt;&gt;Auth: 返回记录
    
    Auth-&gt;&gt;Auth: 校验清单：
    Note over Auth: ✅ token_hash是否匹配&lt;br/&gt;✅ 是否过期 (expires_at &gt; now())&lt;br/&gt;✅ 是否被撤销 (revoked = false)&lt;br/&gt;✅ 用户是否仍有效&lt;br/&gt;✅ 设备信息是否一致（可选）
    
    alt 校验全部通过
        Auth-&gt;&gt;DB: UPDATE refresh_tokens&lt;br/&gt;SET last_used_at = now(),&lt;br/&gt;    last_used_ip = current_ip&lt;br/&gt;WHERE id = token_id
        Auth-&gt;&gt;Auth: 生成新access_token&lt;br/&gt;（可选：延长refresh_token有效期）
        Auth--&gt;&gt;Frontend: 返回新access_token
        Frontend-&gt;&gt;Frontend: 更新access_token
        Frontend-&gt;&gt;Backend: 重试原请求（新token）
        Backend--&gt;&gt;Frontend: 返回资源
    else 校验失败
        Auth--&gt;&gt;Frontend: 401/403 (刷新失败)
        Frontend-&gt;&gt;Frontend: 清除所有token
        Frontend-&gt;&gt;User: 跳转登录页
    end

    Note over User,DB: 5. 主动登出
    User-&gt;&gt;Frontend: 点击登出
    Frontend-&gt;&gt;Auth: POST /logout&lt;br/&gt;refresh_token
    Auth-&gt;&gt;DB: UPDATE refresh_tokens&lt;br/&gt;SET revoked = true&lt;br/&gt;WHERE id = token_id
    Auth--&gt;&gt;Frontend: 登出成功
    Frontend-&gt;&gt;Frontend: 清除本地token
    Frontend--&gt;&gt;User: 已登出

    Note over User,DB: 6. 安全场景：密码修改
    User-&gt;&gt;Frontend: 修改密码
    Frontend-&gt;&gt;Auth: POST /change-password
    Auth-&gt;&gt;DB: UPDATE refresh_tokens&lt;br/&gt;SET revoked = true&lt;br/&gt;WHERE user_id = current_user_id
    Note over DB: 撤销该用户所有refresh_token&lt;br/&gt;（强制所有设备重新登录）
    Auth--&gt;&gt;Frontend: 密码修改成功
    Frontend-&gt;&gt;Frontend: 清除本地token
    Frontend--&gt;&gt;User: 请重新登录
&lt;/pre&gt;</description>
    </item>
  </channel>
</rss>
