尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
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校验提高了系统安全性。
RELATED

相关推荐

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

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

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

📅 2026/10/19 11:33:34
球墨铸铁件加工后防锈水浸泡了,装配时还是发现锈点,怎么回事?

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

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

📅 2026/10/19 11:35:44
网络安全——AI正在学会入侵,而且比所有人预想的都快

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

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

📅 2026/10/20 15:25:57
MORE NEWS

更多资讯

📰

SolidWorks二次开发模板:基于.NET Framework的可交付插件工程化实践

简介:本资源是一套开箱即用的SolidWorks C#二次开发模板工程,面向机械设计工程师、CAD自动化开发者及高校相关专业学习者,解决从零搭建插件项目、理解SDK交互机制与快速实现功能扩展的入门难题。压缩包含99个文件,涵盖18个核心C#源…

📰

Linux磁盘I/O监控:iostat与iotop实战排查

1. 为什么需要监控磁盘 I/O:从一次“查不出原因的卡顿”说起你有没有遇到过这种情况:应用响应突然变慢,CPU 和内存占用却都在正常范围内,看日志也没报错,可服务就是像陷在泥潭里一样。排查到最后才发现,罪魁…

📰

一文梳理AI Infra知识全景:算力、存储、训练、推理与平台

做AI项目做了几年,我一直有个很尴尬的处境:模型结构、损失函数、调参技巧这些聊起来头头是道,可一旦扯到GPU集群怎么规划、数据管线为什么卡住、推理服务为什么线上长尾延迟爆炸,就有点心虚。直到有一次亲手把一个训练任务搞挂&am…

📰

从模糊命名到清晰边界:以rea为例谈代码可维护性重构

1. 从“rea”这个标题说起:一个被低估的缩写背后藏着什么第一次看到“rea”这个标题的时候,我承认自己愣了几秒。没有正文,没有关键词,没有摘要描述,就孤零零三个字母。但恰恰是这种极简的输入,反而让我觉得…

📰

Hive实现树形结构展开:三种方案对比与工程实践

1. 树形结构为什么要展开:从组织架构到评论盖楼的实际场景很多数仓同学第一次看到“hive实现树形结构展开”这个需求时,心里想的都是:这不就是递归查询吗?但真正动手就会发现,Hive 在这方面比常见的关系型数据库麻烦得…

📰

计算机图形学速通指南:从渲染管线到软渲染器的全景地图

计算机图形学这个领域有个挺有意思的现象:网上的教程多到看不完,但能把一个三角形从顶点数据变成屏幕上像素这件事从头到尾讲清楚的人,少之又少。我准备写一套《计算机图形学速通指南笔记》的时候,一直在想第一篇到底该从哪讲起。…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬