当前位置: 首页 > news >正文

【深入理解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能够根据用户身份处理订单相关的操作

区分有状态认证和无状态认证

有状态认证介绍:

传统的基于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】从认证授权到网关安全

最近的项目学习中&#xff0c;在进行登陆模块的用户信息验证这一部分又用到了JWT的一些概念和相关知识&#xff0c;特在此写了这篇文章、方便各位笔者理解JWT相关概念 目录 先来理解JWT是什么&#xff1f; 区分有状态认证和无状态认证 有状态认证 VS 无状态认证 JWT令牌的…...

esp工程报错:something went wrong when trying to build the project esp-idf 一种解决办法

最近上手了正点原子esp32s3板子&#xff0c;环境采用的是vscodeesp-idf插件。导入了正点原子的demo测试&#xff0c;每次都报这个错误无法建造。也不是网上说的ninja error&#xff0c;不是中文路径的问题。 在终端中查看&#xff0c;发现是缺少了git。&#xff08;我这里没有…...

基于MATLAB红外弱小目标检测MPCM算法复现

摘要&#xff1a;本文详细介绍了一种基于人类视觉系统特性的红外弱小目标检测算法——Multiscale patch-based contrast measure (MPCM)。该算法通过增强目标与背景的对比度&#xff0c;有效检测红外图像中的弱小目标&#xff0c;并在MATLAB环境中进行了复现与实验验证。 关键…...

java基础面试篇

目录 1.概念 1.1说一下Java的特点 1.2Java为什么是跨平台的&#xff1f; 1.3 JVM、JDK、JRE三者关系&#xff1f; 1.4为什么Java解释和编译都有&#xff1f; 1.5 jvm是什么&#xff1f; 1.6 编译型语言和解释型语言的区别&#xff1f; 1.7 Python和Java区别是什么&#…...

Java Map实现类面试题

Java Map实现类面试题 HashMap Q1: HashMap的实现原理是什么&#xff1f; HashMap基于哈希表实现&#xff0c;使用数组链表红黑树&#xff08;Java 8&#xff09;的数据结构。 public class HashMapPrincipleExample {// 模拟HashMap的基本结构public class SimpleHashMap&…...

Vue2+Three.js加载并展示一个三维模型(提供Gitee源码)

目录 一、案例截图 二、安装Three.js 三、代码实现 四、Gitee源码 一、案例截图 二、安装Three.js npm install three 三、代码实现 模型资源我是放在public文件夹下面的&#xff1a; 完整代码&#xff1a; <template><div><div ref"container&qu…...

Spark内存并行计算框架

spark核心概念 spark集群架构 spark集群安装部署 spark-shell的使用 通过IDEA开发spark程序 1. Spark是什么 Apache Spark™ is a unified analytics engine for large-scale data processingspark是针对于大规模数据处理的统一分析引擎 spark是在Hadoop基础上的改进&…...

DeepSeek等LLM对网络安全行业的影响

大家好,我是AI拉呱,一个专注于人工智领域与网络安全方面的博主,现任资深算法研究员一职,兼职硕士研究生导师;热爱机器学习和深度学习算法应用,深耕大语言模型微调、量化、私域部署。曾获多次获得AI竞赛大奖,拥有多项发明专利和学术论文。对于AI算法有自己独特见解和经验…...

【QT】QLinearGradient 线性渐变类简单使用教程

目录 0.简介 1&#xff09;qtDesigner中 2&#xff09;实际执行 1.功能详述 3.举一反三的样式 0.简介 QLinearGradient 是 Qt 框架中的一个类&#xff0c;用于定义线性渐变效果&#xff08;通过样式表设置&#xff09;。它可以用来填充形状、背景或其他图形元素&#xff0…...

可狱可囚的爬虫系列课程 15:防盗链反爬虫的处理

一、防盗链了解 防盗链是一种技术手段&#xff0c;主要用于防止其他网站通过直接链接的方式使用本网站的资源&#xff08;如图片、文件等&#xff09;&#xff0c;从而节省带宽和服务器资源。当其他网站尝试直接链接到受保护的资源时&#xff0c;服务器会根据设置的规则判断请求…...

Vue组件:从使用到原理的深度解析

一、什么是Vue组件&#xff1f; 组件是Vue的核心特性之一&#xff0c;它允许开发者将UI拆分为独立可复用的代码片段。每个组件本质上是一个Vue实例&#xff0c;具有自己的&#xff1a; 模板&#xff08;Template&#xff09; 数据&#xff08;Data&#xff09; 方法&#xf…...

SpringBoot接入DeepSeek(硅基流动版)+ 前端页面调试

文章目录 前言正文一、项目环境二、项目代码2.1 pom.xml2.2 DeepSeekController.java2.3 启动类2.4 logback-spring.xml2.5 application.yaml2.6 index.html 三、页面调试3.1 参数提示3.2 开始请求3.3 手动断开 前言 作为一个Java程序员&#xff0c;了解前沿科技技术&#xff…...

Lua的table(表)

Lua表的基本概念 Lua中的表&#xff08;table&#xff09;是一种多功能数据结构&#xff0c;可以用作数组、字典、集合等。表是Lua中唯一的数据结构机制&#xff0c;其他数据结构如数组、列表、队列等都可以通过表来实现。 表的实现 Lua的表由两部分组成&#xff1a; 数组部分…...

图片爬取案例

修改前的代码 但是总显示“失败” 原因是 修改之后的代码 import requests import os from urllib.parse import unquote# 原始URL url https://cn.bing.com/images/search?viewdetailV2&ccidTnImuvQ0&id5AE65CE4BE05EE7A79A73EEFA37578E87AE19421&thidOIP.TnI…...

【Python爬虫(90)】以Python爬虫为眼,洞察金融科技监管风云

【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取,还涉及数据处理与分析。无论是新手小白还是进阶开发…...

idea + Docker + 阿里镜像服务打包部署

一、下载docker desktop软件 官网下载docker desktop&#xff0c;需要结合wsl使用 启动成功的画面(如果不是这个画面例如一直处理start或者是stop需要重新启动&#xff0c;不行就重启电脑) 打包成功的镜像在这里&#xff0c;如果频繁打包会导致磁盘空间被占满&#xff0c;需…...

C#模拟退火算法

模拟退火算法&#xff1a;寻找最优解的神奇 “退火之旅” 在生活中&#xff0c;我们都见过铁匠打铁。铁匠把烧得通红的铁块不断捶打&#xff0c;然后慢慢冷却&#xff0c;这样打造出来的金属制品才更坚固耐用。模拟退火算法就从这个退火过程中获得灵感&#xff0c;在计算机的数…...

网络安全防御模型

目录 6.1 网络防御概述 一、网络防御的意义 二、被动防御技术和主动防御技术 三、网络安全 纵深防御体系 四、主要防御技术 6.2 防火墙基础 一、防火墙的基本概念 二、防火墙的位置 1.防火墙的物理位置 2.防火墙的逻辑位置 3. 防火墙的不足 三、防火墙技术类型 四…...

APP自动化实战

APP自动化能做什么&#xff1f; 请看示例&#xff08;实现批量的视频&#xff0c;封面功能复用能力&#xff08;实现效果参考抖音号&#xff1a;71403700901&#xff09; APP自动化实战&#xff0d;操作剪映APP PO模式 1. PO模式介绍 PO&#xff08;Page Object&#xff09;…...

Unity基础——资源导入

一.资源来源 1.Assert Store&#xff08;Unity资源官方网站&#xff09; &#xff08;1&#xff09;用于制作游戏的优质资源 | Unity Asset Store &#xff08;2&#xff09;或则通过Unity项目打开 2.外部资源 &#xff08;1&#xff09;淘宝 &#xff08;2&#xff09;找外…...

JMeter性能问题

性能测试中TPS上不去的几种原因 性能测试中TPS上不去的几种原因_tps一直上不去-CSDN博客 网络带宽 连接池 垃圾回收机制 压测脚本 通信连接机制 数据库配置 硬件资源 压测机 业务逻辑 系统架构 CPU过高什么原因 性能问题分析-CPU偏高 - 西瓜汁拌面 - 博客园 US C…...

形式化数学编程在AI医疗中的探索路径分析

一、引言 1.1 研究背景与意义 在数字化时代,形式化数学编程和 AI 形式化医疗作为前沿领域,正逐渐改变着我们的生活和医疗模式。形式化数学编程是一种运用数学逻辑和严格的形式化语言来描述和验证程序的技术,它通过数学的精确性和逻辑性,确保程序的正确性和可靠性。在软件…...

DeepSeek开源周Day1:FlashMLA引爆AI推理性能革命!

项目地址&#xff1a;GitHub - deepseek-ai/FlashMLA 开源日历&#xff1a;2025-02-24起 每日9AM(北京时间)更新&#xff0c;持续五天&#xff01; ​ 一、开源周震撼启幕 继上周预告后&#xff0c;DeepSeek于北京时间今晨9点准时开源「FlashMLA」&#xff0c;打响开源周五连…...

nginx 配置https

参考文档&#xff1a;nginx 文档 -- nginx官网|nginx下载安装|nginx配置|nginx教程 配置 HTTPS 服务器 HTTPS 服务器优化 SSL 证书链 单个 HTTP/HTTPS 服务器 基于名称的 HTTPS 服务器 具有多个名称 的 SSL 证书 服务器名称指示 兼容性 要配置 HTTPS 服务器&#xff0c;ssl…...

GhostBottleneck; InvertedResidual;Squeeze and Excite 是什么,怎么用

GhostBottleneck; InvertedResidual;Squeeze and Excite 是什么,怎么用 目录 GhostBottleneck; InvertedResidual;Squeeze and Excite 是什么,怎么用GhostBottleneckInvertedResidualSqueeze and Excite(SE)GhostBottleneck 概念: GhostBottleneck 是在轻量级神经网…...

Docker启动ES容器打包本地镜像

文章目录 1、安装 Docker2、下载镜像3、查看已下载的镜像4、 保存和加载镜像5、.tar 文件与 Docker 镜像的关系6、如何从 .tar 文件加载 Docker 镜像7、为什么需要 .tar 文件&#xff1f;8、ES 8.x版本无法启动8.1 问题原因8.2 解决方案8.3 提交容器为新镜像 1、安装 Docker 如…...

XXE漏洞:原理、危害与修复方法详解

目录 一、XXE漏洞概述二、XXE漏洞原理三、XXE漏洞危害1. 任意文件读取2. 命令执行3. 拒绝服务攻击(DoS)4. SSRF攻击四、XXE漏洞修复方法1. 禁用外部实体JavaPythonPHP2. 输入验证和过滤3. 安全配置服务器4. 升级解析器版本五、总结一、XXE漏洞概述 XXE(XML External Entity…...

android keystore源码分析

架构 Android Keystore API 和底层 Keymaster HAL 提供了一套基本的但足以满足需求的加密基元&#xff0c;以便使用访问受控且由硬件支持的密钥实现相关协议。 Keymaster HAL 是由原始设备制造商 (OEM) 提供的动态加载库&#xff0c;密钥库服务使用它来提供由硬件支持的加密服…...

状态模式

状态&#xff08;State&#xff09;模式属于行为型模式的一种。 状态模式允许对象在其内部状态改变时改变其行为&#xff0c;使其看上去就像改变了自身所属的类一样。 状态模式是为了把一大串if...else...的逻辑给分拆到不同的状态类中&#xff0c;使得将来增加状态比较容易。…...

C++ | 面向对象 | 类

&#x1f47b;类 &#x1f47e;语法格式 class className{Access specifiers: // 访问权限DataType variable; // 变量returnType functions() { } // 方法 };&#x1f47e;访问权限 class className {public:// 公有成员protected:// 受保护成员private:// 私有成员 }…...

鸿蒙-AVPlayer

compileVersion 5.0.2&#xff08;14&#xff09; 音频播放 import media from ohos.multimedia.media; import common from ohos.app.ability.common; import { BusinessError } from ohos.base;Entry Component struct AudioPlayer {private avPlayer: media.AVPlayer | nu…...

Android移动应用开发实践-1-下载安装和简单使用Android Studio 3.5.2版本(频频出错)

一、下载安装 1.Android Studio3.5.2下载地址&#xff1a;Android Studio3.5.2下载地址 其他版本下载地址&#xff1a;其他版本下载地址 2.安装教程&#xff08;可以多找几个看看&#xff09; 安装 | 手把手教你Android studio 3.5.2安装&#xff08;安装教程&#xff09;_a…...

从.m3u8到.mp4:使用批处理脚本完成视频处理的完整指南

这里介绍一个Windows批处理脚本&#xff08;Windows Batch Script&#xff09;&#xff0c;主要用于处理 .m3u8 ts 视频文件的下载和合并功能。 以下是程序的主要功能和逻辑流程&#xff1a; 功能概述 参数检查与路径处理&#xff1a; 检查是否传递了文件或文件夹路径作为参数…...

qt5的中文乱码问题,QString、QStringLiteral 为 UTF-16 编码

qt5的中文乱码问题一直没有很明确的处理方案。 今天处理进程间通信时&#xff0c;也遇到了qt5乱码问题&#xff0c;一边是设置的GBK&#xff0c;一边设置的是UTF8&#xff0c;单向通信约定采用UTF8。 发送端保证发的是UTF8字符串&#xff0c;因为UTF8在网络数据包中没有字节序…...

Gurobi 并行计算的一些问题

最近尝试用 gurobi 进行并行计算&#xff0c;即同时用多个 cpu 核计算 gurobi 的 model&#xff0c;但是发现了不少问题。总体来看&#xff0c;gurobi 对并行计算的支持并不是那么好。 gurobi 官方对于并行计算的使用在这个网址&#xff0c;并有下面的大致代码&#xff1a; i…...

Vue3 中如何实现响应式系统中的依赖收集和更新队列的解耦?

一、问题解析&#xff1a;为什么需要解耦&#xff1f; 在响应式系统中&#xff0c;依赖收集​&#xff08;追踪数据与视图的关联关系&#xff09;和更新队列​&#xff08;批量处理数据变化带来的副作用&#xff09;是两个核心但职责不同的模块。 Vue3 通过以下设计实现解耦&…...

vue项目中动态添加类名样式不生效问题

一、问题描述 在vue项目中使用:class{tableContent: summary}给元素动态添加了类名tableContent&#xff0c;运行代码后查看类名已经添加成功但样式并未生效。 二、问题产生原因并解决 刚开始把样式写在了<style lang"scss" scoped></style>中&#x…...

供应链管理系统--升鲜宝门店收银系统功能解析,登录、主界面、会员 UI 设计图(一)

供应链管理系统--升鲜宝门店收银系统功能解析&#xff0c;登录、主界面 会员 UI 设计图&#xff08;一&#xff09;...

用AI写游戏3——deepseek实现kotlin android studio greedy snake game 贪吃蛇游戏

项目下载 https://download.csdn.net/download/AnalogElectronic/90421306 项目结构 就是通过android studio 建空项目&#xff0c;改下MainActivity.kt的内容就完事了 ctrlshiftalts 看项目结构如下 核心代码 MainActivity.kt package com.example.snakegame1// MainA…...

设计模式的引入

面向对象设计原则 1. 软件设计固有的复杂性2. 面向对象设计原则2.1 引入2.2 依赖倒置原则2.3 开放封闭原则2.4 单一职责原则2.5 Liskov 替换原则&#xff08; LSP&#xff09;2.6 接口隔离原则&#xff08; ISP&#xff09;2.7 优先使用对象组合&#xff0c;而不是类继承2.8 封…...

P8697 [蓝桥杯 2019 国 C] 最长子序列

P8697 [蓝桥杯 2019 国 C] 最长子序列 题目 分析代码 题目 分析 先分析一波xdm 题意呢就是在s中找有多少个能和t匹配的字符&#xff0c;注意&#xff1a;连续匹配&#xff0c;输出连续的次数 欧克&#xff0c;开始分析&#xff0c;首先&#xff0c;哎~字母&#xff01;还强调…...

基于阿里云PAI平台快速部署DeepSeek大模型实战指南

一、DeepSeek大模型&#xff1a;企业级AI应用的新标杆 1.1 为什么选择DeepSeek&#xff1f; 近期&#xff0c;DeepSeek系列模型凭借其接近GPT-4的性能和开源策略&#xff0c;成为全球开发者关注的焦点。在多项国际评测中&#xff0c;DeepSeek-R1模型在推理能力、多语言支持和…...

【java进阶】java多态深入探讨

前言 在Java的编程宇宙中,多态是极为关键的概念,它宛如一条灵动的纽带,串联起面向对象编程的诸多特性,赋予程序宛如生命般的动态活力与高度灵活性。透彻理解多态,不仅是提升代码质量的关键,更是开启高效编程大门的钥匙。 一、多态的定义与本质 多态,从概念层面来讲,…...

蓝桥杯备赛-拔河

问题描述 小明是学校里的一名老师&#xff0c;他带的班级共有 nn 名同学&#xff0c;第 ii 名同学力量值为 aiai​。在闲暇之余&#xff0c;小明决定在班级里组织一场拔河比赛。 为了保证比赛的双方实力尽可能相近&#xff0c;需要在这 nn 名同学中挑选出两个队伍&#xff0c…...

Zookeeper(67) Zookeeper在HBase中的应用是什么?

Zookeeper 在 HBase 中起到了至关重要的作用&#xff0c;主要用于协调和管理 HBase 集群中的多个组件。具体来说&#xff0c;Zookeeper 在 HBase 中的应用包括以下几个方面&#xff1a; Master 选举&#xff1a;HBase 集群中可以有多个 Master 节点&#xff0c;但只有一个处于…...

java后端开发day20--面向对象进阶(一)--static继承

&#xff08;以下内容全部来自上述课程&#xff09; 1.static–静态–共享 static表示静态&#xff0c;是java中的一个修饰符&#xff0c;可以修饰成员方法&#xff0c;成员变量。 1.静态变量 被static修饰的成员变量&#xff0c;叫做静态变量。 特点&#xff1a; 被该类…...

IDEA使用Maven方式构建SpringBoot项目

1、环境准备 确保你已经安装了以下工具&#xff1a; Java JDK&#xff08;推荐 JDK 8 或更高版本&#xff09; IntelliJ IDEA&#xff08;推荐使用最新版本&#xff09; 2、创建 Spring Boot 项目 &#xff08;1&#xff09; 打开 IntelliJ IDEA。 &#xff08;2&#xff09…...

Spring Boot2.0之十 使用自定义注解、Json序列化器实现自动转换字典类型字段

前言 项目中经常需要后端将字典类型字段值的中文名称返回给前端。通过sql中关联字典表或者自定义函数不仅影响性能还不能使用mybatisplus自带的查询方法&#xff0c;所以推荐使用自定义注解、Json序列化器&#xff0c;Spring的缓存功能实现自动转换字典类型字段。以下实现Spri…...

C#问题解决方案 --- 生成软件hash,生成文件hash

生成软件hash值&#xff1a; private string GetEXEHashString() {//获得软件哈希值Process currProcess Process.GetCurrentProcess();string filePath currProcess.MainModule.FileName;string hashEXE string.Empty;using (FileStream fs new FileStream(filePath, Fil…...

云计算如何解决延迟问题?

在云计算中&#xff0c;延迟&#xff08;latency&#xff09;指的是从请求发出到收到响应之间的时间间隔。延迟过高可能会严重影响用户体验&#xff0c;特别是在需要实时响应的应用中&#xff0c;如在线游戏、视频流、金融交易等。云计算服务如何解决延迟问题&#xff0c;通常依…...