接手微服务项目的踩坑与重构:鉴权链路、补偿机制与 API 文档整理

接手了一套微服务项目,代码看起来挺像回事,仔细一看全是坑 这套项目表面上看骨架搭得不错——Spring Cloud Alibaba 全家桶、17 个 Maven 模块、9 个微服务、分库分表、ES 双写、SkyWalking 链路追踪,基础中间件能怼的全都怼上去了。 但真的跑起来、改起来、审计起来,才发现业务逻辑有不少问题:鉴权链路缺半截、下单不扣库存、@NoLogin 散落几十个地方、JWT 只存了 username 导致全链路 Redis 查重。基础设施搭得好,不等于项目能经受住实际业务场景的考验。 本文将项目里几个典型问题摆出来,聊聊踩坑过程和修复思路,同时配图说明关键流程的改造前后对比。 JWT claims:只放 username,剩下的全塞 Redis 第一个让人困惑的设计点在于 JWT 的使用方式。看下 Token 生成代码: // UserTokenHelper.java — 原实现 public String generateToken(String username, String json) { String token = Jwts.builder() .setSubject(username) // ← 只放了 username .setExpiration(generateExpired()) .signWith(SignatureAlgorithm.HS512, tokenSecret) .compact(); redisUtil.set(getTokenKey(username), token, 3600); // Redis 存一份 redisUtil.set(getUserKey(username), json, 3600); // 用户完整信息也存 Redis return token; } JWT 的 claims 字段本质上设计来承载结构化信息,签名保证不被篡改。但这里把它当成了一个随机字符串来用——JWT 里只放了 sub: "admin",userId、角色、权限全部丢进 Redis。 ...

二月 28, 2023 · 4 分钟 · 849 字 · yaomingye

B/S架构常见网络攻击与SpringBoot/Cloud防御实践——XSS、CSRF、SQL注入、DDoS与JWT安全的攻防图谱

B/S攻防:五类攻击与Spring体系的应对 📌 前置知识:本文假设读者用过 Spring Boot、知道 Cookie/Session/Token 的基本概念。Spring Security 的 Filter Chain 不熟没关系——每段防御代码会说明它在过滤器链中的位置。 XSS:当用户输入变成了可执行脚本 跨站脚本攻击(Cross-Site Scripting)的本质是:攻击者把 JavaScript 塞进用户输入,服务器原样输出到 HTML,浏览器执行了这段恶意脚本。 sequenceDiagram participant Attacker as 攻击者 participant Victim as 受害者浏览器 participant Server as 有漏洞的服务器 participant DB as 数据库 Attacker->>Server: "POST /comment 提交评论\n内容: script stealCookie()" Server->>DB: 存入评论(未做转义) Victim->>Server: GET /article?id=123 Server->>DB: 查询评论列表 DB-->>Server: 返回含脚本的评论 Server-->>Victim: "script stealCookie()\n浏览器解析并执行" Victim->>Attacker: "恶意脚本执行\nCookie 被发送到攻击者服务器" Spring Boot 的防御分三层: ① 输出转义——Thymeleaf 默认做 // Thymeleaf 模板中默认对变量做 HTML 转义 // <div th:text="${comment.content}"> → < 变成 &lt;,脚本失效 // 如果你用 JSP 或手动拼 HTML,务必用 escapeHtml: String safe = HtmlUtils.htmlEscape(userInput); ② 输入过滤——Spring 全局拦截 ...

二月 24, 2023 · 5 分钟 · 927 字 · yaomingye

OAuth 2.0 + JWT 单点登录

OAuth 2.0 + JWT 单点登录实战 一、从一个登录按钮说起 你在商家后台(merchant.shop.com)点了"登录",页面跳转到了一个统一的登录页,你输入账号密码,然后又跳回了商家后台——你已经在系统里了。然后你打开运营后台(ops.shop.com),不用再输密码,直接进去了。 这就是单点登录(SSO)。背后有三个角色在协作: sequenceDiagram participant Browser as 浏览器 participant Biz as 业务系统\nmerchant.shop.com participant SSO as 认证中心\nsso.company.com Browser->>Biz: 1. 访问商家后台 Biz-->>Browser: 2. 302: 去认证中心登录 Browser->>SSO: 3. 跳转到登录页 SSO-->>Browser: 4. 返回登录页面 Browser->>SSO: 5. 提交用户名密码 SSO-->>Browser: 6. 302: 登录成功,回业务系统 Browser->>Biz: 7. 带着凭证回商家后台 Biz->>SSO: 8. 后端验证凭证 SSO-->>Biz: 9. 返回用户身份 Biz-->>Browser: 10. 登录成功,进入系统 这里面有两个关键问题: 第 5 步中,用户的密码交给了谁? 答案:只交给了认证中心。业务系统从头到尾都没见过用户的密码。 第 7 步中,浏览器带回的"凭证"是什么? 答案:是一个一次性的授权码(code),不是用户名密码,也不是最终的身份令牌。 这就是 OAuth 2.0 授权码模式的核心思路:用户密码只给认证中心,业务系统通过一个间接的"授权码"来确认用户身份。 二、逐帧拆解:一次登录的完整交互 下面以一个真实场景走一遍完整流程。三个参与者: 参与者 对应系统 职责 浏览器 用户正在用的 Chrome / Edge 用户操作的入口,负责跳转和提交凭据 业务系统 CRM 应用(crm.company.com:8080) 用户真正想用的系统,需要确认"你是谁" 认证中心 SSO 服务器(sso.company.com:9000) 唯一能验证用户名密码的地方,签发身份令牌 下面是完整的交互流程——请重点关注每个角色在每一步做了什么: ...

九月 22, 2022 · 13 分钟 · 2617 字 · yaomingye

SSO 与 JWT+Redis 的定位差异

SSO 与 JWT+Redis 的定位差异:Token格式、管理策略、认证架构三个层次 🤔 一、一个常见的学习困惑 很多开发者在学习鉴权体系时会遇到这样的困惑: 已经理解了 JWT 的三段式结构,知道它是无状态的 Token 格式 也理解了 JWT + Redis 混合方案,知道它能解决 Token 主动撤销的问题 然后听到"微服务用 SSO(单点登录)",去查资料后发现 SSO 也用 JWT 于是产生疑问:JWT + Redis 方案和 SSO 是什么关系?是不是同一个东西的不同叫法?如果不是,区别在哪? 这三个概念确实容易混淆,因为它们都围绕"鉴权"这个话题,但它们解决问题的层次完全不同。下面用三个明确的定义开篇: 概念 本质 解决什么问题 JWT Token 数据格式 Token 如何编码用户信息、如何防篡改 JWT + Redis Token 管理策略(单服务内部) 单个服务如何签发、验证、撤销 Token SSO(单点登录) 认证架构模式(跨服务) 多个服务之间如何共享登录状态 💼 二、从一个具体的业务场景理解差异 假设你所在的公司有三个系统: OA 办公系统(oa.company.com)—— 审批、考勤 CRM 客户系统(crm.company.com)—— 客户管理 BI 报表系统(bi.company.com)—— 数据分析 🏝️ 2.1 没有 SSO 时:每个系统各自鉴权 sequenceDiagram participant U as 用户 participant OA as OA系统 participant CRM as CRM系统 participant BI as BI系统 Note over U,BI: 用户需要分别登录3个系统 U->>OA: 打开OA → 输入用户名密码 OA-->>U: 登录成功 (OA的Token) U->>CRM: 打开CRM → 再次输入用户名密码 CRM-->>U: 登录成功 (CRM的Token) U->>BI: 打开BI → 第三次输入用户名密码 BI-->>U: 登录成功 (BI的Token) 每个系统都有自己独立的用户表、独立的登录接口、独立签发 Token。用户需要在三个系统之间各登录一次。这里的每个系统内部,可能各自使用了 JWT + Redis 管理自己的 Token——但这和"用户只需登录一次"是两个不同的问题。 ...

九月 21, 2022 · 9 分钟 · 1755 字 · yaomingye

JWT 与双令牌机制详解

JWT 与双令牌机制详解:从结构原理到 Java 代码实现 🤔 一、一个登录请求背后的困境 你写完了一个登录接口,用户提交用户名密码,服务端验证通过后创建 Session,把用户信息存进去,返回一个 JSESSIONID 的 Cookie。后续请求自动带上这个 Cookie,服务端从 Session 中取出用户信息——这是最传统的 Session 认证方式。 @PostMapping("/login") public String login(HttpSession session, @RequestBody LoginRequest req) { User user = userService.verify(req.getUsername(), req.getPassword()); if (user == null) { return "用户名或密码错误"; } session.setAttribute("currentUser", user); // 存入Session return "登录成功"; } @GetMapping("/info") public User info(HttpSession session) { return (User) session.getAttribute("currentUser"); // 从Session取 } 这段代码在单机部署时没有问题。但当你部署到 3 台服务器、前面挂了一个 Nginx 负载均衡时,问题就出现了: ...

九月 20, 2022 · 12 分钟 · 2361 字 · yaomingye

JWT + Redis 双令牌鉴权实战

JWT + Redis 双令牌鉴权实战:生产环境下的 Token 主动失效与过期管理方案 🔥 一、一个真实的生产事故 先看一段在中小项目中常见的鉴权代码: // 登录:生成JWT,返回给客户端 public String login(String username, String password) { User user = userService.verify(username, password); return Jwts.builder() .setSubject(user.getId().toString()) .setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) .signWith(SECRET_KEY) .compact(); } // 拦截器:验证JWT签名和过期时间 public boolean preHandle(HttpServletRequest request, ...) { String token = request.getHeader("Authorization"); Claims claims = Jwts.parserBuilder() .setSigningKey(SECRET_KEY).build() .parseClaimsJws(token).getBody(); // Token签名正确且未过期 → 放行 return true; } 这段代码能跑吗?能。有安全隐患吗?有,而且很严重。 ...

九月 19, 2022 · 19 分钟 · 3916 字 · yaomingye

Spring Security + JWT 企业级鉴权实战

Spring Security + JWT 企业级鉴权实战:从零概念到完整代码实现 阅读前提:本文假设你已经会使用 Spring Boot 写基本的 CRUD 接口。如果你从未接触过 Spring Security,从这篇开始即可。 本文按照"先搞懂概念 → 教程版完整实现 → 生产版逐项升级 → 验证排错“的顺序组织。如果你只想快速跑通一个能用的版本,读完 Part 1 后直接看 Part 2 即可;如果你想理解企业级项目的真实做法,需要完整读完。 Part 1:先搞懂要做什么 在写任何代码之前,先把三个问题搞清楚:认证和授权到底是什么?Spring Security 怎么运作的?JWT 是什么? 一、从一个没有防护的接口说起 假设你用 Spring Boot 写了一个用户管理接口: @RestController @RequestMapping("/api/admin") public class AdminController { @GetMapping("/users") public List<User> listAllUsers() { // 返回系统中所有用户信息 return userService.findAll(); } @DeleteMapping("/users/{id}") public String deleteUser(@PathVariable Long id) { userService.deleteById(id); return "删除成功"; } } 启动项目后,任何人只要知道 URL,就能直接访问这些接口——不需要登录,不需要权限。这在企业生产环境中是不可接受的。 ...

九月 18, 2022 · 23 分钟 · 4761 字 · yaomingye
Cat Radio