【深入理解JWT】从认证授权到网关安全
最近的项目学习中,在进行登陆模块的用户信息验证这一部分又用到了JWT的一些概念和相关知识,特在此写了这篇文章、方便各位笔者理解JWT相关概念

目录
先来理解JWT是什么?
区分有状态认证和无状态认证
有状态认证 VS 无状态认证
JWT令牌的优点
JWT令牌的三个部分
测试生成JWT令牌
实例:携带令牌访问资源服务
网关 — 完善架构
先来理解JWT是什么?
JWT全称为JSON Web Token是一种开放标准(RFC 7519),主要用于在网络应用间安全地传递信息,是一种基于JSON的紧凑,独立的令牌格式,通常用于身份验证和信息交换
它的主要使用场景:
用于身份验证,用于登录和后续请求的验证
举一个现实中的例子,假设我们正在使用淘宝平台,登录流程大致如下:
1.当我们输入了用户名和密码,点击”登录“,网站服务器验证你的凭证
2.验证成功后,服务器生成了一个JWT返回给你的浏览器
3.如果继续浏览商品或下单时,浏览器会在请求中携带这个JWT,网站服务器通过验证JWT确认你的身份
4.确认身份后,就能访问购物车,下单或查看订单详情
在分布式系统或多服务架构中,JWT负责在不同的服务之间传递用户信息
假设有一个电商平台,其中包括两个服务:服务A(用户服务)和服务B(订单服务),用户通过服务A登陆后,需要将用户信息传递给服务B完成订单操作,以便服务B能够根据用户身份处理订单相关的操作
区分有状态认证和无状态认证
(JWT采用无状态认证方式,先了解传统有状态认证,后了解无状态认证,更突出JWT设计理念)
有状态认证介绍:
传统的基于session的方式是有状态认证,用户登录成功将用户的身份信息存储在服务端,这样会加大服务端的存储压力,这种方式也不适合在分布式系统中应用
如下图,当用户访问应用服务,每个应用服务都会去服务器查看session信息,如果session中没有该用户则说明用户没有登录,此时就会重新认证,而解决这个问题的方法就是Session复制,Session黏贴

无状态认证:
如果是基于令牌技术在分布式系统中实现认证,则服务端不用存储session,可以将用户身份信息存储在令牌中,用户认证通过后认证服务颁发令牌给用户,用户将令牌存储在客户端,去访问应用服务时携带令牌去访问,服务端从JWT解析出用户信息,这个过程就是无状态认证

有状态认证 VS 无状态认证
有状态流程:
用户登录 —> 服务端创建Session并存储用户信息 —> 返回Session ID给客户端 —> 客户端携带 Session ID访问应用服务 —> 服务端根据Session ID查找用户信息 —> 返回响应
优点:
1.安全性较高:用户的信息存储在服务端,客户端仅持有Session ID,不容易泄露敏感数据
2.易于控制:服务端可以随时使Session失效,比如用户注销,超时
缺点:
1.服务端存储压力大:每个用户的Session信息都需要存储在服务端,用户量较大时对内存和存储资源要求高
2.不适合分布系统:在分布式系统中,Session需要共享,比如通过Redis集群,复杂性增加,新增服务器时 还需要同步Session数据
无状态认证流程:
用户登录 —> 服务端生成Token(令牌)并返回给客户端 —> 客户端携带Token(令牌)访问应用服务 —> 服务端解析Token(令牌)获取用户信息 —>返回响应
优点:
1.无需存储信息:服务端无需存储用户信息,适合分布式系统和微服务架构
2.无需同步:新增服务器时无需同步数据,降低了运维成本
3.实现跨域共享:令牌可以轻松实现跨域资源共享
缺点:
1.令牌长度较大,比Session ID长,可能会增加网络开销
2.Token在过期前始终有效,无法像Session一样主动注销
3.JWT的Payload是Base64编码的,可以被解码,不适合存储敏感信息
分析这两个有无状态认证,总结出以下几点:
有状态认证:更适合传统Web应用,像银行系统这样需要严格会话控制的场景
无状态认证:更适合分布式系统,微服务架构,移动端和前后端分离的应用
JWT令牌的优点
1.JWT基于JSON,易于解析:
JWT的Header和Payload是Base64编码的,解码后可以直接解析为JSON对象,开发者可以轻松地从JWT中提取所需的信息,比如用户ID,角色..
{"userId": 123,"username": "john_doe","role": "admin"
}
2.可以在令牌中自定义丰富的内容:
JWT的Payload部分可以包含任意自定义的声明(claims),可以在Payload中添加用户角色,权限,过期时间等信息,如果要添加新的信息(如添加用户邮箱,地址等),只需在Payload中添加新的字段,无需修改现有逻辑
{"userId": 123,"username": "john_doe","role": "admin",//新增了邮箱 电话等信息"email": "john@example.com","exp": 1698765432
}
3.通过非对称加密算法以及数字签名技术,防止篡改,安全性高
JWT的Signature部分是通过Header和Payload使用指定的算法(如HMAC SHA256或RSA SHA256)生成的
如果攻击者修改了JWT的Header或Payload,签名将不再匹配,验证时会失败
同样的,缺点如下:JWT令牌较长,占的内存存储空间较大

JWT令牌的示例
JWT令牌的三个部分
以上这段JWT令牌代码包含了三部分,用点号(.)分隔
Header:头部包括令牌的类型(既JWT)以及使用的哈希算法,HMAC SHA256或RSA
Base64编码前的JSON
{"alg": "HS256", //alg:签名算法,这里是HS256"typ": "JWT" //令牌类型,这里是JWT
}
Base64编码后的Header(以上令牌示例图第一部分)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload:第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,可以存放jwt提供的信息字段,比如:iss(签发者),exp(过期时间戳),sub(面向的用户)等,也可以自定义字段
Base64编码前的Payload
{"aud": ["res1"], //令牌的目标受众,这里是["rset"]"user_name": "zhangsan",//用户名"scope": ["all"], //权限范围"exp": 1664254672, //令牌的过期时间(Unix时间戳)"authorities": ["p1"], //用户的权限,这里是["p1"]"jti": "88912b2d-5d05-4c14-bbc3-fde9977febc6",//令牌的唯一标识符"client_id": "c1" //客户端ID,这里是c1
}
Base64编码后的Payload(以上令牌示例图第二部分)
eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQyNTQ2NzIsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6Ijg4OTEyYjJk
LTVkMDUtNGMxNC1iYmMzLWZkZTk5NzdmZWJjNiIsImNsaWVudF9pZCI6ImMxIn0
Signature(签名):签名是通过对Header和Payload进行加密生成的,将Header和Payload分别进行Base64Url编码
编码后将Header,Payload和Signature用点号(.)拼接起来,形成完整的JWT
//拼接Header + payload + 对Header和Payload的签名(验证令牌是否被篡改)
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret) //secret:签名所使用的密钥
(以上令牌示例图)最后一部分就是编码后的Signature
wkDBL7roLrvdBG2oGnXeoXq-zZRgE9IVV2nxd-ez_oA
为什么JWT可以防止篡改?
第三部分使用签名算法对Header和Payload的内容进行签名,常见的签名算法是HS256,常见的还有MD5,SHA等...签名算法需要使用密钥进行签名,密钥不对外公开,并且签名是不可逆的,如果第三方更改了内容那么服务器验证签名就会失败,要想保证验证签名正确必须保证内容,密钥与签名前一致

从上图中可以看出认证服务和资源服务使用了相同的密钥,这叫做对称加密,对称加密效率高,如果一旦密钥泄露可以伪造JWT令牌
JWT还可以使用非对称加密,认证服务自己保留私钥,将公钥下发给受信任的客户端,资源服务,公钥和私钥是配对的,成对的公钥和私钥才可以正常加密和解密,非对称加密效率低但相比对称加密,非对称加密更安全一些
测试生成JWT令牌
在认证服务中配置JWT令牌服务,即可实现生成JWT格式的令牌
package com.xuecheng.auth.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;import java.util.Arrays;/*** @author Administrator* @version 1.0**/
@Configuration
public class TokenConfig {private String SIGNING_KEY = "mq123";@AutowiredTokenStore tokenStore;// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }@Autowiredprivate JwtAccessTokenConverter accessTokenConverter;@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());//使用JwtTokenStore替代默认的InMemoryTokenStore,表示令牌以JWT格式存储}//配置JWT的签名密钥和转换逻辑@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey(SIGNING_KEY);return converter;}//令牌管理服务 定义令牌服务的核心行为(如令牌生成,刷新,有效期等)@Bean(name="authorizationServerTokenServicesCustom")public AuthorizationServerTokenServices tokenService() {DefaultTokenServices service=new DefaultTokenServices();service.setSupportRefreshToken(true);//支持刷新令牌service.setTokenStore(tokenStore);//令牌存储策略TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));service.setTokenEnhancer(tokenEnhancerChain);service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天return service;}
}
重启认证服务
使用httpclient通过密码模式申请令牌
### 密码模式
POST {{auth_host}}/oauth/token?client_id=XcWebApp&client_secret=
XcWebApp&grant_type=password&username=zhangsan&password=123
生成的JWT示例如下:
{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOi
J6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQzMzE2OTUsImF1dGhvcml0aWVzIj
pbInAxIl0sImp0aSI6ImU5ZDNkMGZkLTI0Y2ItNDRjOC04YzEwLTI1NmIzNGY4ZGZjYyIsImNsaW
VudF9pZCI6ImMxIn0.-9SKI-qUqKhKcs8Gb80Rascx-JxqsNZxxXoPo82d8SM", //生成的JWT令牌"token_type": "bearer","refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ
6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJlOWQzZDBmZC0yNGNiLTQ0YzgtOGMxMC0y
NTZiMzRmOGRmY2MiLCJleHAiOjE2NjQ1ODM2OTUsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6I
mRjNTRjNTRkLTA0YTMtNDIzNS04MmY3LTFkOWZkMmFjM2VmNSIsImNsaWVudF9pZCI6ImMxIn0.Ws
w1Jc-Kd_GFqEugzdfoSsMY6inC8OQsraA21WjWtT8","expires_in": 7199,"scope": "all","jti": "e9d3d0fd-24cb-44c8-8c10-256b34f8dfcc"
}
1.access-token部分:这部分是生成的JWT令牌,用于访问资源使用
2.token_type: 这部分的bearer是在REC6750中定义的一种token类型,在携带JWT访问资源时需要在head中加入bearer jwt令牌内容
###校验jwt令牌
POST {{auth_host}}/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJzdHUxIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTY2NDM3MTc4MCwiYXV0aG9yaXRpZXMiOlsicDEiXSwianRpIjoiZjBhM2NkZWItMzk5ZC00OGYwLTg4MDQtZWNhNjM4YWQ4ODU3IiwiY2xpZW50X2lkIjoiYzEifQ.qy46CSCJsH3eXWTHgdcntZhzcSzfRQlBU0dxAjZcsUw
3.refresh_token:当JWT令牌快过期时使用刷新令牌可以再次生成JWT令牌
4.expires_in:过期时间(秒)
5.scope:令牌的权限范围,服务端可以根据令牌的权限范围去对令牌授权
6.jti:令牌的唯一标识
我们可以通过check_token接口校验JWT令牌
###校验jwt令牌
POST {{auth_host}}/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJ
dLCJ1c2VyX25hbWUiOiJzdHUxIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTY2
NDM3MTc4MCwiYXV0aG9yaXRpZXMiOlsicDEiXSwianRpIjoiZjBhM2NkZWItM
zk5ZC00OGYwLTg4MDQtZWNhNjM4YWQ4ODU3IiwiY2xpZW50X2lkIjoiYzEifQ.
qy46CSCJsH3eXWTHgdcntZhzcSzfRQlBU0dxAjZcsUw
响应如下:
{"aud": ["res1"],"user_name": "zhangsan","scope": ["all"],"active": true,"exp": 1664371780,"authorities": ["p1"],"jti": "f0a3cdeb-399d-48f0-8804-eca638ad8857","client_id": "c1"
}
实例:携带令牌访问资源服务
拿到了JWT令牌下一步就要携带令牌去访问资源服务中的资源,比如在线教育项目:内容管理服务模块,客户端申请到JWT令牌,携带JWT去内容管理服务查询课程信息,此时内容管理服务要对JWT进行校验,只有JWT合法才可以继续访问,如图所示流程:

案例演示:在内存管理服务(在线教育系统)中配置OAuth2资源服务,并测试携带JWT令牌访问受保护接口,比较有无JWT令牌会产生什么结果
1.在内容管理服务的content-api工程中添加依赖
<!--引入Spring Security和OAuth2支持,使服务能验证JWT令牌并保护API端点-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId> <!--依赖1-->
</dependency>
<dependency><groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <!--依赖2-->
</dependency>
加入的依赖用途:
依赖1:继承基本安全功能(认证,授权)
依赖2:集成OAuth2协议,支持资源服务器配置
2.在内容管理服务的content-api中添加TokenConfig配置类
目的:用于配置JWT相关组件 — JWT的签名密钥和存储方式
@Configuration
public class TokenConfig {private String SIGNING_KEY = "mq123";@Beanpublic JwtAccessTokenConverter accessTokenConverter(){JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey(SIGNING_KEY);return converter;}@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}
}
3.添加资源服务配置类ResourceServerConfig
目的:配置资源服务器的安全规则,通过@EnableResourceServer启动资源服务器的功能
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {public static final String RESOURCE_ID = "xuecheng-plus";@AutowiredTokenStore tokenStore;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(RESOURCE_ID).tokenStore(tokenStore).stateless(true);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable() // 禁用 CSRF 保护.authorizeRequests() //配置对请求的授权策略.antMatchers("/r/**", "/course/**").authenticated() // 指定 "/r/" 和 "/course/" 这两个路径需要进行身份认证才能访问。.anyRequest().permitAll(); // 允许所有其他请求(除了上面指定的路径之外)都可以被访问,不需要进行身份认证。}
}
其中.antMatchers("/r/**", "/course/**").authenticated() ,这些路径请求必须携带有效令牌,否则返回401 Unauthorized
重启内容管理服务,使用httpclient测试:
1.访问根据课程id查询课程接口
### 查询课程信息
GET http://localhost:63040/content/course/2
返回结果:
{"error": "unauthorized","error_description": "Full authentication is required to access this resource"
}
从返回信息可知:当前没有认证(测试未在请求体提供有效的JWT令牌,资源服务器拦截了请求)
下边携带JWT令牌访问接口
1.申请JWT令牌:采用密码模式申请令牌
###### 密码模式
POST {{auth_host}}/auth/oauth/token?client_id=MsWebApp&client_secret=MsWebApp&grant_type=password&username=Kyle&password=123
2.携带JWT令牌访问资源服务地址
### 携带token访问资源服务
GET http://localhost:63040/content/course/2
Authorization: Bearer
//JWT令牌
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6a
GFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQzMzM0OTgsImF1dGhvcml0aWVzIjpbInA
xIl0sImp0aSI6IjhhM2M2OTk1LWU1ZGEtNDQ1Yy05ZDAyLTEwNDFlYzk3NTkwOSIsImN
saWVudF9pZCI6ImMxIn0.73eNDxTX5ifttGCjwc7xrd-Sbp_mCfcIerI3lGetZto
3.如果携带JWT令牌,且JWT令牌正确,则正常访问资源服务的内容
{
"id": 129,
"companyId": 12293202020,
"companyName": null,
"name": "臭臭",
"users": "君子 ",
"tags": "",
"mt": "1-5",
"st": "1-5-4",
"grade": "204003",
"teachmode": "200002",
"description": null,
"pic": "/mediafiles/2023/03/03/76ac562669dc346992af9dd039060e7b.jpg",
"createDate": "2025-02-25 17:17:07",
"changeDate": "2025-02-26 11:09:31",
"createPeople": null,
"changePeople": null,
"auditStatus": "203002",
"status": "203001",
"charge": "201000",
"price": 0.0,
"originalPrice": null,
"qq": "",
"wechat": "",
"phone": "",
"validDays": 365,
"mtName": "人工智能",
"stName": "计算机科学"
}
如果不正确则报令牌无效的错误,例如:
{"error": "invalid_token","error_description": "Cannot convert access token to JSON"
}
测试获取用户身份
JWT令牌中同样也记录了用户身份信息,当客户端携带JWT访问资源服务,资源服务验证签名,通过后将前两部分的内容还原即可取出用户的身份信息,并将用户身份信息放在了SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份
以在线教育系统查看课程接口为例,进入查询课程接口的代码中,添加获取用户身份的代码
@ApiOperation("根据课程id查询课程基础信息")
@GetMapping("/course/{courseId}")
public CourseBaseInfoDto getCourseBaseById(@PathVariable("courseId") Long courseId){//取出当前用户身份Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();System.out.println("当前用户身份为:"+principal);return courseBaseInfoService.getCourseBaseInfo(courseId);
}
测试时需要注意:
1.首先在资源服务配置中指定安全拦截机制/course/开头的请求需要认证,既请求/course/{courseId}接口需要携带JWT令牌且签证通过
2.认证服务生成JWT令牌将用户身份信息写入令牌,目前还是将用户信息硬编码并暂放到内存中
如下:
@Bean
public UserDetailsService userDetailsService() {//这里配置用户信息,这里暂时使用这种方式将用户存储在内存中InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(User.withUsername("liwenwen").password("123").authorities("p1").build());manager.createUser(User.withUsername("yiyangyang").password("456").authorities("p2").build());return manager;
}
3.我们在使用密码模式生成JWT令牌时用的是lliwenwen的信息,所以JWT令牌中存储了liwenwen的信息,那么在资源服务中应该取出liwenwen的信息才对
了解了这些内容,使用HttpClient测试接口重启内容管理服务,跟踪取到的用户身份是正确的,结果如下:
当前用户身份为:liwenwen
至此,用户登录通过了认证服务颁发了JWT令牌,客户端携带JWT访问资源服务,资源服务对JWT的合法性进行验证,如下图:

网关 — 完善架构
但是这样的操作流程似乎遗漏了架构中非常重要的组件:网关,加上之后并完善后如下图所示

注:所有访问微服务的请求都要经过网关,在网关进行用户身份的认证可以将很多非法的请求拦截到微服务以外,这叫做网关认证
网关的职责:
1.网络白名单维护:针对不用认证的URL全部放行
2.校验JWT的合法性:除了白名单剩下的就是需要认证的请求,网关需要验证JWT的合法性,JWT合法则说明用户身份合法,否则说明身份不合法则拒绝继续访问
网关负责授权码?
答:网关不负责授权,对请求的授权操作在各个微服务进行,因为微服务最清楚用户有哪些权限访问哪些接口
下面实现网关认证
1.在网关工程添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId>
</dependency>
2.将网关鉴权配置类添加在项目的config包下
GatewayAuthFilter类:
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {//白名单private static List<String> whitelist = null;static {//加载白名单 白名单中的路径无需认证即可访问(如登录接口,静态资源)try (InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");) {Properties properties = new Properties();properties.load(resourceAsStream);Set<String> strings = properties.stringPropertyNames();whitelist = new ArrayList<>(strings);} catch (Exception e) {log.error("加载/security-whitelist.properties出错:{}", e.getMessage());e.printStackTrace();}}@Autowiredprivate TokenStore tokenStore;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String requestUrl = exchange.getRequest().getPath().value();AntPathMatcher pathMatcher = new AntPathMatcher();//白名单放行for (String url : whitelist) {if (pathMatcher.match(url, requestUrl)) {return chain.filter(exchange);}}//检查token是否存在String token = getToken(exchange);if (StringUtils.isBlank(token)) {return buildReturnMono("没有认证", exchange);}//判断是否是有效的tokenOAuth2AccessToken oAuth2AccessToken;try {oAuth2AccessToken = tokenStore.readAccessToken(token);boolean expired = oAuth2AccessToken.isExpired();if (expired) {return buildReturnMono("认证令牌已过期", exchange);}return chain.filter(exchange);} catch (InvalidTokenException e) {log.info("认证令牌无效: {}", token);return buildReturnMono("认证令牌无效", exchange);}}/*** 获取token*/private String getToken(ServerWebExchange exchange) {String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");if (StringUtils.isBlank(tokenStr)) {return null;}String token = tokenStr.split(" ")[1];if (StringUtils.isBlank(token)) {return null;}return token;}private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {ServerHttpResponse response = exchange.getResponse();String jsonString = JSON.toJSONString(new RestErrorResponse(error));byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);DataBuffer buffer = response.bufferFactory().wrap(bits);response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");return response.writeWith(Mono.just(buffer));}@Overridepublic int getOrder() {return 0;}
}
(该过滤器用于拦截所有经过网关的请求,检查请求是否在白名单中,或者是否携带有效的JWT令牌)
1.如果请求在白名单中,直接放行
2.如果请求不在白名单中,检查是否携带有效的JWT令牌
令牌有效 -----> 放行
令牌无效或过期 -----> 返回401 Unauthorized错误
RestErrorResponse类:
(用于封装错误响应信息,在RESTful API中返回统一的错误格式)
public class RestErrorResponse implements Serializable {private String errMessage;public RestErrorResponse(String errMessage){this.errMessage= errMessage;}public String getErrMessage() {return errMessage;}public void setErrMessage(String errMessage) {this.errMessage = errMessage;}
}
SecurityConfig类
(通过@EnableWebFluxSecurity注解启动了Spring WebFlux的安全功能,并定义一个SecurityWebFilterChain Bean,用于配置请求的安全拦截规则)
@EnableWebFluxSecurity
@Configuration
public class SecurityConfig {//安全拦截配置@Beanpublic SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {return http.authorizeExchange().pathMatchers("/**").permitAll().anyExchange().authenticated().and().csrf().disable().build();}
}
TokenConfig类
(该Spring配置类用于配置JWT相关的组件,包括TokenStore和JwtAccessTokenConverter)
@Configuration
public class TokenConfig {String SIGNING_KEY = "mq123";//定义了TokenStore Bean,用来存储和管理JWT令牌@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}//用于将OAuth2访问令牌与JWT进行转换@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey(SIGNING_KEY);return converter;}
}
配置白名单文件security-whitelist.properties
/auth/**=认证地址
/content/open/**=内容管理公开访问接口
/media/open/**=媒资管理公开访问接口
重启网关工程,进行测试
1.申请令牌
2.通过网关访问资源服务
这里访问内容管理服务
### 通过网关访问资源服务
GET http://localhost:63010/content/course/2
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX
25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQzNjIzMTAsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6Ijc2OTkwMGNiLWM1ZjItNGRiNC1hZWJmLWY1MzgxZDQxZWMyZCIsImNsaWVudF9pZCI6ImMxIn0.lOITjUgYg2HCh5mDPK9EvJJqz-tIupKVfmP8yWJQIKs
当token正确时就可以正常访问资源服务,token验证失败返回token无效
{"errMessage": "认证令牌无效"
}
至此,了解了使用Spring Security进行认证授权的过程,本篇只对Spring Security做了一个简单的介绍,要掌握并结合各种认证方式实现系统的登录认证模块开发,还需要参考一些实例
相关文章:
【深入理解JWT】从认证授权到网关安全
最近的项目学习中,在进行登陆模块的用户信息验证这一部分又用到了JWT的一些概念和相关知识,特在此写了这篇文章、方便各位笔者理解JWT相关概念 目录 先来理解JWT是什么? 区分有状态认证和无状态认证 有状态认证 VS 无状态认证 JWT令牌的…...
学习路之PHP --TP6异步执行功能 (无需安装任何框架)
学习路之PHP --异步执行功能 (无需安装任何框架) 简介一、工具类二、调用三、异步任务的操作四、效果: 简介 执行异步任务是一种很常见的需求,如批量发邮箱,短信等等执行耗时任务时,需要程序异步执行&…...
DeepSeek-R1:GPU编程自动化加速的新纪元
摘要 DeepSeek-R1是由斯坦福大学和普林斯顿大学研究者共同开发的项目,其自研的CUDA核心在性能测试中取得了卓越成绩,超越了o1和Claude 3.5 Sonnet,位居榜首。尽管DeepSeek-R1目前仅在约20%的任务中实现了对PyTorch Eager模式的性能超越&#…...
CSS 对齐:深入理解与技巧实践
CSS 对齐:深入理解与技巧实践 引言 在网页设计中,元素的对齐是至关重要的。一个页面中元素的对齐方式直接影响到页面的美观度和用户体验。CSS 提供了丰富的对齐属性,使得开发者可以轻松实现各种对齐效果。本文将深入探讨 CSS 对齐的原理、方法和技巧,帮助开发者更好地掌握…...
vue深拷贝:1、使用JSON.parse()和JSON.stringify();2、使用Lodash库;3、使用深拷贝函数(采用递归的方式)
文章目录 引言三种方法的优缺点在Vue中,实现数组的深拷贝I JSON.stringify和 JSON.parse的小技巧深拷贝步骤缺点:案例1:向后端请求路由数据案例2: 表单数据处理时复制用户输入的数据II 使用Lodash库步骤适用于复杂数据结构和需要处理循环引用的场景III 自定义的深拷贝函数(…...
九、数据治理架构流程
一、总体结构 《数据治理架构流程图》(Data Governance Architecture Flowchart) 水平结构:流程图采用水平组织,显示从数据源到数据应用的进程。 垂直结构:每个水平部分进一步划分为垂直列,代表数据治理的…...
【数据结构】 最大最小堆实现优先队列 python
堆的定义 堆(Heap)是一种特殊的完全二叉树结构,通常分为最大堆和最小堆两种类型。 在最大堆中,父节点的值总是大于或等于其子节点的值; 而在最小堆中,父节点的值总是小于或等于其子节点的值。 堆常用于实…...
51c自动驾驶~合集52
我自己的原文哦~ https://blog.51cto.com/whaosoft/13383340 #世界模型如何推演未来的千万种可能 驾驶世界模型(DWM),专注于预测驾驶过程中的场景演变,已经成为追求自动驾驶的一种有前景的范式。这些方法使自动驾驶系统能够更…...
服务 ‘Sql Server VSS writer‘ (SQLWriter) 在安装 LocalDB 时无法启动
安装Microsoft Visual C 2015-2019 Redistributable (x64)...
【我的 PWN 学习手札】House of Husk
House of Husk House of Husk是利用格式化输出函数如printf、vprintf在打印输出时,会解析格式化字符如%x、%lld从而调用不同的格式化打印方法(函数)。同时C语言还提供了注册自定义格式化字符的方法。注册自定义格式化字符串输出方法…...
Nmap使用指南
Nmap使用指南 Nmap (网络映射器) 是一款强大的应用网络扫描和安全核查工具,适合于网络管理和安全专家。本文将介绍Nmap的基本使用方法,包括基本命令和常用功能。 1. 基本使用方式 Nmap的基本命令格式如下: nmap [选项] 目标地址目标地址 可…...
傅里叶分析
傅里叶分析之掐死教程(完整版)更新于2014.06.06 要让读者在不看任何数学公式的情况下理解傅里叶分析。 傅里叶分析不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维模式。但不幸的是,傅里叶分析的公式看起来太复…...
从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(五) 实现登录功能
1.登录页面 完善登录页面 和注册差不多 直接copy signUpPage 内容 再稍微修改下 import { useState } from "react"; import { useAuthStore } from "../store/useAuthStore"; import { MessageSquare,Mail,Lock,Eye, EyeOff,Loader2} from "lucide…...
基于多层感知机(MLP)实现MNIST手写体识别
实现步骤 下载数据集处理好数据集确定好模型(初始化模型参数等等)确定优化函数(损失函数也称为目标函数)和优化方法(一般选用随机梯度下降 SDG )进行模型的训练进行模型的评估 import torch import torch…...
如何使用useContext进行全局状态管理?
在 React 中,使用 useContext 进行全局状态管理是一种有效的方法,尤其在需要在多个组件之间共享状态时。useContext 允许你在组件树中传递数据,而无需通过每个组件的 props 逐层传递。以下是关于如何使用 useContext 进行全局状态管理的详细指…...
【机器学习】Logistic回归#1基于Scikit-Learn的简单Logistic回归
主要参考学习资料: 《机器学习算法的数学解析与Python实现》莫凡 著 前置知识:线性代数-Python 目录 问题背景数学模型类别表示Logistic函数假设函数损失函数训练步骤 代码实现特点 问题背景 分类问题是一类预测非连续(离散)值的…...
8.Dashboard的导入导出
分享自己的Dashboard 1. 在Dashboard settings中选择 JSON Model 2. 导入 后续请参考第三篇导入光放Dashboard,相近...
next.js-学习2
next.js-学习2 1. https://nextjs.org/learn/dashboard-app/getting-started2. 模拟的数据3. 添加样式4. 字体,图片5. 创建布局和页面页面导航 1. https://nextjs.org/learn/dashboard-app/getting-started /app: Contains all the routes, components, and logic …...
视频推拉流EasyDSS直播点播平台授权激活码无效,报错400的原因是什么?
在当今数字化浪潮中,视频推拉流 EasyDSS 视频直播点播平台宛如一颗璀璨的明珠,汇聚了视频直播、点播、转码、精细管理、录像、高效检索以及时移回看等一系列强大功能于一身,全方位构建起音视频服务生态。它既能助力音视频采集,精准…...
【论文详解】Transformer 论文《Attention Is All You Need》能够并行计算的原因
文章目录 前言一、传统 RNN/CNN 存在的串行计算问题二、Transformer 如何实现并行计算?三、Transformer 的 Encoder 和 Decoder 如何并行四、结论 前言 亲爱的家人们,创作很不容易,若对您有帮助的话,请点赞收藏加关注哦ÿ…...
Fisher信息矩阵(Fisher Information Matrix,简称FIM)
Fisher信息矩阵简介 Fisher信息矩阵(Fisher Information Matrix,简称FIM)是统计学和信息理论中的一个重要概念,广泛应用于参数估计、统计推断和机器学习领域。它以统计学家罗纳德费希尔(Ronald Fisher)的名…...
基础设施安全(Infrastructure Security)是什么?
基础设施安全(Infrastructure Security)指的是保护IT基础设施(包括物理和云端的服务器、网络设备、存储、数据库等)免受网络攻击、数据泄露、未授权访问、系统故障等威胁的各种安全措施和技术。 1. 基础设施安全的主要组成部分 &…...
[杂学笔记]OSI七层模型作用、HTTP协议中的各种方法、HTTP的头部字段、TLS握手、指针与引用的使用场景、零拷贝技术
1.OSI七层模型作用 物理层:负责光电信号的传输,以及将光电信号转化为二进制数据数据链路层:主要负责将收到的二进制数据进一步的封装为数据帧报文。同时因为数据在网络中传递的时候,每一个主机都能够收到报文数据,该层…...
Framework层JNI侧Binder
目录 一,Binder JNI在整个系统的位置 1.1 小结 二,代码分析 2.1 BBinder创建 2.2 Bpinder是在查找服务时候创建的 2.3 JNI实现 2.4 JNI层android_os_BinderProxy_transact 2.5 BPProxy实现 2)调用IPCThreadState发送数据到Binder驱动…...
Windows 图形显示驱动开发-WDDM 3.2-自动显示切换(九)
面板驱动程序 显示器驱动程序是根据从 EDID 生成的即插即用 (PnP) 硬件 ID 加载的。 由于 EDID 保持不变,当任何一个 GPU 控制内部面板时,都会加载面板驱动程序。 这两个驱动程序将显示相同的亮度功能。 因此,加载应该不会造成任何问题&…...
Excel大文件拆分
import pandas as pddef split_excel_file(input_file, output_prefix, num_parts10):# 读取Excel文件df pd.read_excel(input_file)# 计算每部分的行数total_rows len(df)rows_per_part total_rows // num_partsremaining_rows total_rows % num_partsstart_row 0for i i…...
OpenCV计算摄影学(7)HDR成像之多帧图像对齐的类cv::AlignMTB
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 该算法将图像转换为中值阈值位图(Median Threshold Bitmap,MTB): 1.位图生成:…...
JWT+redis实现三大令牌管理方案深度解析
三种令牌管理方案对比与评估 1. 仅续期Redis(不生成新令牌) 实现原理 通过延长Redis中的令牌有效期维持会话,JWT本身不包含动态过期时间。 优点 ✅ 低开销:无需生成新令牌,减少JWT签名计算成本。 ✅ 简单实现&#x…...
北京大学DeepSeek提示词工程与落地场景(PDF无套路免费下载)
近年来,大模型技术飞速发展,但许多用户发现:即使使用同一款 AI 工具,效果也可能天差地别——有人能用 AI 快速生成精准方案,有人却只能得到笼统回答。这背后的关键差异,在于提示词工程的应用能力。 北京大…...
Axure PR 9 中继器 03 翻页控制
大家好,我是大明同学。 接着上期的内容,这期内容,我们来了解一下Axure中继器图表翻页控制。 预览地址:https://pvie5g.axshare.com 翻页控制 1.打开上期RP 文件,在元件库中拖入一个矩形,宽值根据业务实际…...
