
登录方式账号密码前端加密传输建议HTTPS 密码加盐哈希比对密码数据库存的是BCrypt/MD5 盐验证通过生成TokenJWT或SessionID返回前端验证码登录短信验证码前端输入手机号后端校验手机号格式发送频率60s 一次生成4~6 位随机数字验证码存入 Rediskey: sms:code:138xxxxvalue: 1234过期时间5分钟调用短信服务商阿里云 / 腾讯云 / 华为云发送短信验证码登录图片验证码前端从后端手机号 验证码后端从 Redis 取出该手机号的验证码通过生成 Token/SessionID验证码必须Redis 存储必须防刷60s、每日限制支持登录注册一体第三方登录微信前端跳转到微信授权 URL用户扫码确认微信回调你的后端 / 前端接口后端调用微信接口用 code 换 access_token openid获取用户信息可选系统登录 / 注册逻辑根据openid/unionid查询你的用户表如果存在 → 直接登录如果不存在自动注册昵称、头像从微信拿绑定 openid/unionid生成系统自己的。 Token/SessionID跳回前端主页。企业统一登录架构统一认证中心 多种登录渠道任意方式登录密码 / 短信 / 微信认证通过生成统一 TokenJWT所有业务系统只认这个 Token登出销毁 Token登录账号密码验证码String sessionId request.getSession().getId(); public MapString, Object getCaptcha(HttpServletRequest request) { // 获取会话 ID用于固定标识 String sessionId request.getSession().getId(); //限流 /* String ip request.getRemoteAddr(); String rateKey captcha:rate: ip sessionId; Long count redisUtil.incr(rateKey); if (count 1) { redisUtil.expire(rateKey, 60, TimeUnit.SECONDS); // 设置1分钟过期 } if (count 40) { throw ResultCodeEnum.BAD_REQUEST.toException(请求太频繁请稍后再试); }*/ try { // 1. 生成随机验证码文本4位 String code RandomUtil.randomString(BASE_CHECK_CODES, 4); String lowerCaseCode code.toLowerCase(); // 2. 构造安全的 Redis 键MD5(key 签名密钥) 验证码小写 String keyPrefix DigestUtil.md5Hex(sessionId : secret,utf-8); String realKey keyPrefix lowerCaseCode; // 3. 删除该前缀下的所有旧验证码防止复用 SetString keys redisUtil.keys(keyPrefix *); if (keys ! null !keys.isEmpty()) { redisUtil.delete(keys); } // 4. 存入 Redis有效期 60 秒 redisUtil.setString(realKey, lowerCaseCode, 120); String fullBase64 RandImageUtil.generate(code); // 5. 返回给前端 MapString, Object result new HashMap(); // result.put(captchaKey, uuid); result.put(image, fullBase64); return result; }catch (Exception e){ throw ResultCodeEnum.BAD_REQUEST.toException(获取验证码失败e.getMessage()); } }基础知识浏览器规定只有协议、域名、端口三者完全相同才算 “同源”。Servlet 是什么本质遵循 Java EE Servlet 规范的接口javax.servlet.Servlet,jakarta.servlet.*;开发者通过实现该接口或继承其实现类来编写业务逻辑。核心作用接收 HTTP 请求GET/POST 等、处理请求如调用服务层、操作数据库、生成 HTTP 响应返回 HTML/JSON 等。运行环境依赖 Servlet 容器如 Tomcat、Jetty、JBoss容器负责创建 Servlet 实例、管理生命周期、处理网络通信。用户请求 → Tomcat容器 → Filter预处理 → DispatcherServlet → Interceptor.preHandle(用户登录) → Controller方法 → Interceptor.postHandle → 视图渲染 → Interceptor.afterCompletion清理用户信息 → Filter后处理 → 响应返回客户端过滤器是Servlet 容器级别的 “粗粒度拦截”适合处理所有请求的通用逻辑如编码、跨域、日志不依赖 Springimport jakarta.servlet.annotation.WebFilter;拦截器Spring 级别的 “细粒度拦截”适合处理 Controller 层的业务逻辑如登录校验、权限控制支持 Spring 依赖注入import org.springframework.web.servlet.HandlerInterceptor;SessionIDSessionID用户身份认证 / 状态保持「有状态认证」的核心标识 —— 服务器为每个用户创建专属的Session内存 / 数据库 / Redis 存储用户数据并返回一个SessionID作为 “凭证”后续请求携带这个 ID服务器通过 ID 找到对应的Session数据SessionID 默认通过JSESSIONIDCookie 传递无 Cookie 时可通过 URL 重写开发中需注意 SessionID 的安全开启 HTTPS加密传输 SessionID防止网络中被截获设置 Cookie 的 Secure/HttpOnly定期更换 ID防止会话劫持用户登录成功后重新生成 SessionID防止固定会话攻击缩短 Session 过期时间根据业务需求调整如 15 分钟减少泄露风险import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.PrintWriter; WebServlet(/session-demo) public class SessionIdServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(text/html;charsetUTF-8); PrintWriter out response.getWriter(); // 1. 获取Session不存在则创建触发SessionID生成 HttpSession session request.getSession(); // 2. 获取SessionID String sessionId session.getId(); out.write(h3当前SessionID sessionId /h3); // 3. 判断是否是新创建的Session第一次访问 if (session.isNew()) { out.write(p这是新会话服务器刚生成SessionID/p); } else { out.write(p这是已有会话服务器通过SessionID找到你的Session/p); } // 4. 往Session中存储用户数据状态保持 session.setAttribute(username, 张三); out.write(p已存储用户数据 session.getAttribute(username) /p); // 5. 获取Session创建时间、过期时间默认30分钟 out.write(pSession创建时间 session.getCreationTime() /p); out.write(pSession过期时间秒 session.getMaxInactiveInterval() /p); out.close(); } }Token以 JWT 为例用户身份认证 / 状态保持「无状态认证」的核心凭证 —— 服务器不存储任何用户状态而是将用户信息如用户名、权限加密 / 签名后生成一个 Token 字符串返回给客户端后续请求携带 Token服务器仅通过验证 Token 的签名和解析内容即可完成身份认证。维度SessionID基于 SessionJWT Token认证模式有状态Stateful无状态Stateless核心数据存储服务器端存储 Session用户信息客户端存储 Token用户信息加密在 Token 中凭证本质仅为 “索引 ID”无业务含义包含完整用户信息加密 / 签名后的 “令牌”传递方式主要通过 CookieJSESSIONID主要通过请求头Authorization也可 Cookie/LocalStorage服务器依赖依赖服务器存储 Session内存 / Redis不依赖服务器存储仅需密钥验证签名分布式 / 集群场景需解决 Session 共享问题如 Redis 集群、Tomcat 集群同步天然支持分布式任意服务器拿到 Token 都能验证过期处理服务器主动销毁 Session或超时自动过期Token 自带过期时间Exp服务器无法主动作废需配合黑名单扩展性差Session 共享增加服务器开销好无存储开销支持跨域 / 多端数据大小SessionID 短32 位左右Token 可能较长包含用户信息需控制 Payload 大小安全风险会话劫持窃取 SessionID、Session 固定攻击令牌泄露Token 被盗即被盗用、无法主动吊销跨域支持弱Cookie 跨域需配置 CORSSameSite强请求头传递 Token不受 Cookie 跨域限制多端适配差APP / 小程序无 Cookie需 URL 重写好APP / 小程序 / 前端均可存储 TokenSaToken一款轻量级的 Java 权限认证框架相比 Shiro/Spring Security 更简洁易用1请求-》携带token-StpUtil.checkLogin();执行登录校验-》stpLogic.getSession().get(userInfo);获取用户信息存入 ThreadLocal或ttl2请求-》未携带token-》跳转登录页面/login-》根据用户名从admin/user/store用户表中获取用户信息校验密码-StpUtil.login(id)-》获取权限-》保存用户信息到sessionstpStoreUtil.getSession().set-》返回用户信息认证StpUtilSaToken 最核心的工具类所有登录 / 权限操作都通过它完成StpUtil.login(id)方法利用了 Cookie 自动注入 tokenStpUtil.checkLogin()校验是否登录StpUtil.login(10001, PC)指定登录设备如PC/APP ; StpUtil.login(10001, () - 自定义Token字符串)自定义Token而非框架生成stpStoreUtil.getSession().setkey,object)自动存储jsonStringToken 生成默认用UUID可通过SaTokenConfig.setTokenStyle()自定义Session 存储默认存内存分布式场景可配置 Redis 存储SaToken 自动适配SaSessionSaToken 封装的会话对象替代原生 HttpSession存储用户登录状态、权限数据TokenSignSaToken 的 Token 生成 / 解析器负责 Token 的创建、验证、过期处理。多账号认证设计两套账号体系比如一个电商系统的user表和admin表StpUtil 类底层stpLogic所以可以自己新建一个新的权限认证类比如StpUserUtil.java。将StpUtil.java类的全部代码复制粘贴到StpUserUtil.java里。更改一下其LoginTypeuser或建立一个StpKit.java门面类声明所有的StpLogic引用。同端多账号登录分别指定定cookie字段名权限权限范围可以控制到页面上的每一个按钮是否显示。单点登录SSO在多个互相信任的系统中用户只需登录一次就可以访问所有系统。OAuth2.0开放授权框架OAuth 2.0 是现代应用第三方登录、开放 API、微服务授权的标准方案功能点SSO单点登录OAuth2.0统一认证支持度高支持度高统一注销支持度高支持度低多个系统会话一致性强一致弱一致第三方应用授权管理不支持支持度高自有系统授权管理支持度高支持度低Client级的权限校验不支持支持度高集成简易度比较简单难度中等适合项目企业内部项目整合企业搭建统一认证授权平台对外开放服务原生 ThreadLocal 和 TransmittableThreadLocal (TTL)原生ThreadLocal可以实现线程内的数据隔离但在线程池复用线程的场景下会失效线程池中的线程是复用的线程执行完任务后不会销毁后续任务复用该线程时会残留上一个任务的ThreadLocal数据脏数据更关键的是子线程无法继承父线程的ThreadLocal上下文即使使用InheritableThreadLocal线程池场景下也会失效TransmittableThreadLocal (TTL)在线程池 / 异步任务场景下安全地传递 ThreadLocal 上下文解决了 线程复用导致的上下文丢失 / 脏数据 问题。关键操作线程池场景下必须包装任务 / 线程池否则 TTL 上下文无法传递执行完任务后建议手动remove()清理上下文import com.alibaba.ttl.threadpool.TtlExecutors; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TTLThreadPoolExample { private static final TransmittableThreadLocalString TTL_CONTEXT new TransmittableThreadLocal(); public static void main(String[] args) throws Exception { // 1. 创建原生线程池 ExecutorService originExecutor Executors.newFixedThreadPool(1); // 2. 用 TTL 包装线程池全局只需要包装一次 ExecutorService ttlExecutor TtlExecutors.getTtlExecutorService(originExecutor); // 3. 父线程设置上下文 TTL_CONTEXT.set(全局包装线程池用户ID1002); // 4. 直接提交任务无需手动包装 ttlExecutor.submit(() - { System.out.println(全局包装线程池获取上下文 TTL_CONTEXT.get()); TTL_CONTEXT.remove(); }).join(); ttlExecutor.shutdown(); } }分布式追踪传递 traceId、spanId 等链路追踪信息微服务上下文传递用户 ID、租户 ID、请求 ID 等日志增强让线程池中的日志也能打印出请求上下文权限控制传递当前登录用户的权限信息