Spring Security + JWT的应用
Spring Security JWT 技术总结一、Spring Security JWT 是什么1. Spring SecuritySpring Security 是 Spring 生态中的安全框架主要解决用户认证Authentication权限控制Authorization请求安全过滤防止非法访问简单来说Spring Security负责判断“你是谁”和“你能干什么”。例如用户登录用户名 密码 | ↓ Spring Security认证 | ↓ 认证成功 | ↓ 生成用户身份信息2. JWTJWTJSON Web Token是一种基于JSON的身份认证协议。它解决的是用户登录之后如何在后续请求中证明自己的身份。传统Session第一次登录 浏览器 | | 用户名密码 ↓ 服务器 生成Session 服务器保存 sessionId - 用户信息 后续请求 浏览器携带sessionId 服务器查询Session缺点服务器需要保存Session集群环境需要Session共享扩展困难JWT方式用户登录 用户名密码 ↓ 服务器生成JWT { mno:10001, nickname:张三, expire:xxx } ↓ 返回Token 客户端保存 后续请求 Authorization: Bearer eyJxxx ↓ 服务器解析JWT 得到用户信息特点无状态不需要服务器保存用户信息适合微服务架构二、为什么项目中需要JWT我的项目是snacknet | | -- gateway网关 | -- users 用户服务(包括管理员用户以及地址) | -- productions 商品服务(包括商品类型以及购物车) | -- orders 订单服务(包括订单以及详情) | -- idGenerator id生成器服务 | -- fileUploda 图片上传服务微服务环境下用户登录发生在user-service但是购物车请求cart-service订单请求order-service如果使用Session问题用户登录 user服务保存session 访问购物车 cart服务 cart不知道user服务的session需要Redis共享SessionSession复制服务间同步复杂。所以改成JWT登录 user-service 生成JWT 返回客户端 客户端以后访问任何服务 Authorization: Bearer token gateway解析JWT 获取用户信息 传递给后端服务使用Spring security jwt的其他原因无状态天生适合微服务扩展服务器不再需要存储 Session无需共享缓存服务实例可以随意弹性伸缩真正做到水平扩展无阻碍。多端适配摆脱 Cookie 束缚JWT 通过请求头传递原生支持 Web、App、小程序等所有客户端完美解决跨域和移动端兼容问题。架构解耦网关统一鉴权即你现在的做法网关层统一解析 JWT 并将用户信息如mno透传给下游微服务业务服务完全不需要引入 Security 依赖大幅消除重复代码让认证层与业务层彻底分离。三、微服务中的JWT整体流程项目最终架构用户 | | Authorization Bearer JWT | ↓ Gateway网关 | JwtGlobalFilter | 解析JWT 获取 mno nickname | ↓ 添加请求头 X-User-Mno:10001 X-User-Nickname:张三 | ↓ --------------------------------- users (这是我的服务名) orders productions ---------------------------------四、项目中的配置1.配置依赖!--引入spring security依赖--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependency!--JWTAPI--dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-api/artifactIdversion0.11.5/version/dependency!--JWT实现--dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-impl/artifactIdversion0.11.5/version/dependency!--JSON序列化--dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-jackson/artifactIdversion0.11.5/version/dependencySpring Security的依赖没有版本是因为我的父项目已经有版本了是直接使用的父项目的版本。在网关gateway这个微服务中我导入的是Spring Security依赖而JWT的依赖我是放在了公共模块这导致了后面导入了公共模块的微服务都需要导入jwt.yml的配置。原因如下products | 引入common | 扫描到JwtUtil | 创建Bean | 需要jwt.secret而我的公共模块包含了Entity、DTO等其他模块也是需要的所以可能的解决方案应该是拆公共模块将公共模块的JWT相关的拆分出去形成一个微服务给网关以及用户服务使用。2.公共模块加入JWT相关2.1 加入UserAuthentication用户认证信息DTO(把JWT里面解析出来的用户身份信息返回给前端或者在服务之间传递)DatapublicclassUserAuthentication{privateLongmno;privateStringnickname;publicUserAuthentication(Longmno,Stringnickname){this.mnomno;this.nicknamenickname;}}2.2 加入JwtPropertiesJwtProperties 是一个JWT配置属性类作用是把 application.yml 里面关于 JWT 的配置映射成 Java 对象方便 JwtUtil 使用。DataConfigurationProperties(prefixjwt)publicclassJwtProperties{/** * JWT密钥 */privateStringsecret;/** * JWT过期时间 * 单位毫秒 */privateLongexpire;}2.3 加入JwtConfig负责Spring注册把 JwtProperties 这个配置类交给 Spring 管理并且绑定 yml 中的配置。这个也可以说是一个小型starter配置入口ConfigurationEnableConfigurationProperties(JwtProperties.class)publicclassJwtConfig{}那为什么要分开些JwtConfig和JwtProperties呢其实是因为职责分离并且方便做Spring Boot自动配置。当然也可以合并合并的写法DataComponent//创建Bean、加入Spring IOCConfigurationProperties(prefixjwt)publicclassJwtProperties{privateStringsecret;privateLongexpire;}2.4. 公共模块加入JwtUtil放在common模块原因我的网关服务和用户服务需要用到。ComponentpublicclassJwtUtil{privatefinalSecretKeykey;privatefinalJwtPropertiesjwtProperties;//相当于AutowriedpublicJwtUtil(JwtPropertiesjwtProperties){this.jwtPropertiesjwtProperties;if(jwtProperties.getSecret()null){thrownewRuntimeException(JWT secret 未配置);}this.keyKeys.hmacShaKeyFor(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8));}/** * 创建JWT */publicStringcreateToken(Longmno,Stringnickname){returnJwts.builder().claim(mno,mno).claim(nickname,nickname)//签发时间.setIssuedAt(newDate())//过期时间.setExpiration(newDate(System.currentTimeMillis()jwtProperties.getExpire())).signWith(key,SignatureAlgorithm.HS256).compact();}/** * 解析JWT */publicClaimsparseToken(Stringtoken){try{returnJwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();}catch(Exceptione){returnnull;}}/** * 获取昵称 */publicStringgetNickname(Stringtoken){ClaimsclaimsparseToken(token);if(claimsnull){returnnull;}returnclaims.get(nickname,String.class);}/** * 获取会员编号 */publicLonggetMno(Stringtoken){ClaimsclaimsparseToken(token);if(claimsnull){returnnull;}returnLong.valueOf(claims.get(mno).toString());}}2. 网关加入Security相关2.1 网关加入SecurityConfigSecurityConfig 是在 Gateway 中开启 Spring Security并配置 Spring Security 不拦截任何请求把真正的 JWT 登录认证交给你自己的 JwtGlobalFilter 处理。不负责登录认证、不负责权限判断。Configuration//Gateway 是基于WebFlux的所以使用Spring Security WebFlux版本EnableWebFluxSecuritypublicclassSecurityConfig{BeanpublicSecurityWebFilterChainsecurityFilterChain(ServerHttpSecurityhttp){returnhttp.csrf(ServerHttpSecurity.CsrfSpec::disable)//关闭跨站请求伪造保护//所有请求放行.authorizeExchange(exchange-exchange.anyExchange().permitAll()).build();}}2.2 加入JwtGlobalFilter它运行在 Gateway 网关层负责检查请求有没有 JWT有就解析用户身份没有或者无效就拒绝请求然后把用户信息传给后面的微服务。ComponentpublicclassJwtGlobalFilterimplementsGlobalFilter,Ordered{privatefinalJwtUtiljwtUtil;/** * 不需要登录的接口 */privatestaticfinalListStringWHITE_LISTList.of(//用户注册登录/user/member/login,/user/member/reg,/user/member/sendCode,//验证码/user/code,//swagger/swagger-ui,/v3/api-docs);publicJwtGlobalFilter(JwtUtiljwtUtil){this.jwtUtiljwtUtil;}OverridepublicMonoVoidfilter(ServerWebExchangeexchange,GatewayFilterChainchain){Stringpathexchange.getRequest().getURI().getPath();// 白名单直接放行if(isWhitePath(path)){returnchain.filter(exchange);}// 获取tokenStringauthorizationexchange.getRequest().getHeaders().getFirst(Authorization);// 没有tokenif(authorizationnull||!authorization.startsWith(Bearer )){exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);returnexchange.getResponse().setComplete();}// 截取tokenStringtokenauthorization.substring(7);// 解析tokenLongmnojwtUtil.getMno(token);StringnicknamejwtUtil.getNickname(token);// token无效if(mnonull||nicknamenull){exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);returnexchange.getResponse().setComplete();}// 注入用户信息ServerHttpRequestrequestexchange.getRequest().mutate().headers(headers-{// 防止客户端伪造headers.remove(X-User-Mno);headers.remove(X-User-Nickname);headers.add(X-User-Mno,String.valueOf(mno));headers.add(X-User-Nickname,nickname);}).build();returnchain.filter(exchange.mutate().request(request).build());}/** * 判断白名单 */privatebooleanisWhitePath(Stringpath){returnWHITE_LIST.stream().anyMatch(path::startsWith);}OverridepublicintgetOrder(){// 优先执行return-100;}}3.修改原来用session的地方3.1 修改登录生成token原来的session.setAttribute(CURRENT_LOGIN_MEMBER,member);改成StringtokenjwtUtil.createToken(member.getMno(),member.getNickname());returnResponseResult.ok().setData(token);登录成功后返回eyJhbGciOiJIUzI1Ni...3.2Controller如何修改以前依赖SessionHttpSessionsession例如Objectobjsession.getAttribute(CURRENT_LOGIN_MEMBER);改成读取Gateway传递的信息RequestHeader(X-User-Mno)Longmno例如购物车以前LongmnogetMno(session);现在GetMapping(/list)publicResponseResultlist(RequestHeader(X-User-Mno)Longmno){}五、我遇到的问题以及解决方案问题1JWT未启动错误org.springframework.beans.factory.BeanCreationException: Error creating bean with name jwtUtil defined in file [D:\ideaIC\ideaIC_project\cloud156\snacknet156-common\target\classes\com\zy\utils\JwtUtil.class]: Failed to instantiate [com.zy.utils.JwtUtil]: Constructor threw exception at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:318) at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:306) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1375) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1212) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) at com.zy.ordersApp.main(ordersApp.java:15) Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zy.utils.JwtUtil]: Constructor threw exception at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:221) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:117) at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:315) ... 19 common frames omitted Caused by: java.lang.RuntimeException: JWT secret 未配置 at com.zy.utils.JwtUtil.init(JwtUtil.java:22) at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:208) ... 21 common frames omitted原因我将gateway的application.yml拆分放到nacos上面去了或者其他服务忘记加jwt.yml造成jwt的配置读不到。解决1.针对把gateway配置到了nacos上去了最好就别配置上去了因为gateway要求性能好而且与其他的公共配置的牵扯较少。如果你实在想配置要去找spring-boot-configuration-processor等相关的。2.针对忘记加jwt.yml,你加上即可。问题2请求被拦截但是地址好像是对的原因我使用postman的时候再添加参数时会打一个空格比如http://localhost:11004/user/test?uid 123456这里uid的后面就多了一个空格在postman发送请求的时候空格也会带上然后到网关就找不到路径可以匹配。解决用postman的时候请注意你的参数后面是否携带空格删掉即可。问题3Redis数据混乱现象Redis 0号库有旧数据现在配置database:0原因在做单体项目转变成微服务的拆分中保存了之前单体项目的数据还没有删除而且我的包名还发生了改变导致503。解决方案清空之前redis里面的数据或者换一个库比如2号等。八、最终项目安全架构用户 | | JWT Token | ↓ Gateway Spring Security JwtGlobalFilter | 验证JWT | 添加用户信息 | ↓ -------------------------------- user-service cart-service order-service product-service -------------------------------- Controller: RequestHeader(X-User-Mno) 获取用户身份六、项目收获总结在项目中我使用Spring Security JWT实现了微服务环境下的统一身份认证。用户登录成功后由用户服务生成JWT Token客户端后续请求携带Token通过Gateway中的GlobalFilter统一解析JWT并将用户信息通过请求头传递给各业务服务。相比传统Session方式JWT解决了微服务环境下Session共享困难的问题实现了无状态认证。同时通过Spring Security配置接口放行规则保证登录注册等公共接口可以访问其他请求经过JWT校验提高了系统安全性。

相关新闻

深圳双诚智能:如何选到靠谱热收缩包装机公司?

深圳双诚智能:如何选到靠谱热收缩包装机公司?

在工业自动化浪潮下,热收缩包装机已成为食品、日化、电子、物流等行业提升包装效率、降低运营成本的核心设备。然而,面对市场上众多品牌与供应商,如何从“设备选型”到“厂商甄别”步步为营,选到一家技术可靠、服务稳健的靠谱企业…

2026/9/28 5:29:07 阅读更多 →
球墨铸铁件加工后防锈水浸泡了,装配时还是发现锈点,怎么回事?

球墨铸铁件加工后防锈水浸泡了,装配时还是发现锈点,怎么回事?

球墨铸铁件在加工后经过防锈水浸泡处理,但装配时拆开包装,工件表面仍然出现了锈点。工艺员检查了防锈水浓度——在标准范围内;检查了pH——正常。但锈点就是出现了。问题往往不在防锈水本身,而在于球墨铸铁件的表面特性。球墨铸铁…

2026/10/13 21:29:29 阅读更多 →
网络安全——AI正在学会入侵,而且比所有人预想的都快

网络安全——AI正在学会入侵,而且比所有人预想的都快

网络安全——AI正在学会入侵,而且比所有人预想的都快 拉斯维加斯,2026年8月。Black Hat进行到一半,安全圈讨论的重点其实已经不是“AI到底会不会改变网络安全”,而是它究竟已经改变了多少。过去几个月里,安全研究人员…

2026/10/2 4:33:01 阅读更多 →

最新新闻

imgsTools图片合并实战:基于Python和Pillow的拼接与批处理

imgsTools图片合并实战:基于Python和Pillow的拼接与批处理

简介:这是一份面向游戏开发者的图片合并工具 imgsTools 完整工程,基于 VALib 图像库与 MFC、GDI 技术实现,用于把多张图片合并为透明 PNG、GIF、JPEG,并生成 Cocos2d 可识别的 .plis 资源清单。PNG 适合精细界面元素与图标&#x…

2026/10/13 21:29:24 阅读更多 →
litellm 实战:统一多模型调用接口与代理层架构设计

litellm 实战:统一多模型调用接口与代理层架构设计

1. 项目概述与核心定位1.1 这个项目到底解决什么问题第一次接触 litellm 是在一个多模型混用的项目里。当时团队同时接入了好几家不同厂商的大模型服务,每家的接口格式、鉴权方式、返回结构都不一样,代码里到处是 if-else 分支,维护起来非常头…

2026/10/13 21:29:24 阅读更多 →
遗传算法优化SVM参数:Python实战与调参避坑指南

遗传算法优化SVM参数:Python实战与调参避坑指南

简介:这份资源面向具备一定Python与机器学习基础、希望掌握超参数调优实战的学习者,聚焦用遗传算法(GA)自动搜索支持向量机(SVM)的最优参数组合,解决人工调参效率低、易陷入局部最优的问题。压缩…

2026/10/13 21:29:24 阅读更多 →
多智能体协作系统实战:用AI Agent搭建虚拟开发团队

多智能体协作系统实战:用AI Agent搭建虚拟开发团队

我花了两周时间,把一个需要四个人并行开发的内部项目,硬生生塞给了一组 AI Agent。说出来你可能不信,最后跑通的产出质量,比我团队里两位中级开发交出来的第一版都要干净。这个想法不是拍脑袋冒出来的。当时我手里压着两个并行需求…

2026/10/13 21:29:24 阅读更多 →
大数据题库拆解:从Hadoop到HBase的备考框架

大数据题库拆解:从Hadoop到HBase的备考框架

简介:这份测试题围绕林子雨《大数据技术原理应用》课程内容整理,适合高校相关专业学生、考研复习者及自学大数据的入门读者巩固理论、考前自测。压缩包内共包含1个Word文档(docx),约58页,整体体积仅76KB&am…

2026/10/13 21:29:24 阅读更多 →
分布式光伏集中监控系统的架构设计:协议适配与告警治理实践

分布式光伏集中监控系统的架构设计:协议适配与告警治理实践

做分布式光伏管理系统之前,我一直觉得“集中监控”四个字不过是把各电站的逆变器数据拉到一个页面上。真到自己动手做的时候才发现,这个页面背后的数据链路、协议适配、告警治理,每一项都是坑。尤其当电站分布在多个省份,屋顶、地…

2026/10/13 21:28:23 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →