恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Spring Security + JWT的应用
首页
资讯中心
/
Spring Security + JWT的应用
Spring Security + JWT的应用
发布时间:2026/8/14 23:51:19
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校验提高了系统安全性。