詳細的閘道器學習可以參考:
https://www.jianshu.com/p/8749dfe9832e
https://www.zhihu.com/column/c_1357481230536216576
https://www.cnblogs.com/bjlhx/category/1273947.html
6.1、閘道器介紹
什麼是閘道器
網絡的關口。資料在網路間傳輸,從一個網路傳輸到另一網路時就需要經過閘道器來做資料的路由和轉發以及資料安全的校驗。
更通俗的來講,閘道器就像是以前園區傳達室的大爺。
-
外面的人要想進入園區,必須經過大爺的認可,如果你是不懷好意的人,肯定被直接攔截。
-
外面的人要傳話或送信,要找大爺。大爺幫你帶給目標人。
現在,微服務閘道器就起到同樣的作用。前端請求不能直接訪問微服務,而是要請求閘道器:
-
閘道器可以做安全控制,也就是登入身份校驗,校驗透過才放行
-
透過認證後,閘道器再根據請求判斷應該訪問哪個微服務,將請求轉發過去
在SpringCloud當中,提供了兩種閘道器實現方案:
-
Netflix Zuul:早期實現,目前已經淘汰
-
Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 實現的響應式的 API 閘道器。
它不能在傳統的 servlet 容器中工作,也不能構建成 war包
。Spring Cloud Gateway 旨在為微服務架構提供一種簡單且有效的 API 路由的管理方式,並基於Filter
的方式提供閘道器的基本功能,例如說安全認證、監控、限流等等。
課堂中我們以SpringCloudGateway為例來講解,官方網站:https://spring.io/projects/spring-cloud-gateway/#learn
6.2、快速入門
我們先看下如何利用閘道器實現請求路由。由於閘道器本身也是一個獨立的微服務,因此也需要建立一個模組開發功能。大概步驟如下:
-
建立閘道器微服務
-
引入SpringCloudGateway、NacosDiscovery依賴
-
編寫啟動類
-
配置閘道器路由
建立一個module
首先,我們要在spring-cloud下建立一個新的module,命名為spring-cloud-gateway,作為閘道器微服務:
引入依賴
1、閘道器服務也是一個微服務,因此需要引入spring-cloud依賴
2、閘道器服務需要把前端的請求轉發給別的微服務,因此需要引入閘道器依賴
3、閘道器服務要做請求轉發就需要知道其他微服務的地址。閘道器服務獲取其他服務地址的方式也是透過註冊中心獲取,因此需要引入nacos相關依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!--閘道器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
discovery:
username: nacos
server-addr: localhost:8848
password: 123456
namespace: 495e2ede-c37a-4258-8e11-72f21b0cf1cd
group: java-coder
gateway:
routes:
- id: order-service # 路由規則id,自定義,唯一
uri: lb://order-service # 路由的目標服務,lb代表負載均衡,會從註冊中心拉取服務列表
predicates: # 路由斷言,判斷當前請求是否符合當前規則,符合則路由到目標服務
- Path=/order/** # 這裡是以請求路徑作為判斷規則
- id: stock-service
uri: lb://stock-service
predicates:
- Path=/stock/**
feign:
okhttp:
enabled: true # 開啟OKHttp功能
package com.java.coder.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication springApplication=new SpringApplication();
springApplication.run(GatewayApplication.class,args);
}
}
spring-boot-starter-web
依賴,正如前面說的,Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 實現的響應式的 API 閘道器。它不能在傳統的 servlet 容器中工作,也不能構建成 war包
透過8081介面直接訪問order-service
:http://localhost:8081/order/feignTest
執行結果如下:
order-service
:
整體的呼叫過程如下:
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter
6.3.1、路由
路由(route)
:路由是閘道器中最基礎的部分,路由資訊包括一個ID、一個目的URI、一組斷言工廠、一組Filter組成。如果斷言為真,則說明請求的URL和配置的路由匹配。
路由示例
spring:
cloud:
gateway:
routes: # 路由陣列[路由 就是指定當請求滿足什麼條件的時候轉到哪個微服務]
- id: order-service # 路由規則id,自定義,唯一
uri: lb://order-service # 路由的目標服務,lb代表負載均衡,會從註冊中心拉取服務列表,uri也可以直接寫地址
order: 1 #路由的優先順序,數字越小級別越高
predicates: # 斷言(就是路由轉發要滿足的條件) ,判斷當前請求是否符合當前規則,符合則路由到目標服務
- Path=/order/** # 當請求路徑滿足Path指定的規則時,才進行路由轉發
filters: #過濾器,請求在傳遞過程中可以透過過濾器對其進行一定的修改
‐StripPrefix=1 #轉發之前去掉1層路徑
gateway的配置檔案
spring.cloud.gateway對應的配置類是
@ConfigurationProperties(GatewayProperties.PREFIX)
@Validated
public class GatewayProperties {
/**
* 屬性字首
*/
public static final String PREFIX = "spring.cloud.gateway";
/**
* 路由列表
*/
@NotNull
@Valid
private List<RouteDefinition> routes = new ArrayList<>();
/**
* 被應用到所有路由的過濾器定義列表,這些過濾器會作用到所有的路由
*/
private List<FilterDefinition> defaultFilters = new ArrayList<>();
private List<MediaType> streamingMediaTypes = Arrays
.asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);
private boolean failOnRouteDefinitionError = true;
private Metrics metrics = new Metrics();
}
routes
其實就是一個RouteDefinition
的列表。
RouteDefinition
RouteDefinition
的定義如下:
public class RouteDefinition {
// 路由id
private String id;
// 路由斷言
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
// 路由過濾器的定義
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
// 要路由的地址
@NotNull
private URI uri;
//
private Map<String, Object> metadata = new HashMap<>();
// 優先順序
private int order = 0;
}
四個屬性含義如下:
-
id
:路由的唯一標示 -
predicates
:路由斷言,其實就是匹配條件 -
filters
:路由過濾條件,後面講 -
uri
:路由目標地址,lb://
代表負載均衡,從註冊中心獲取目標微服務的例項列表,並且負載均衡選擇一個訪問。uri也可以直接配置成訪問路徑,比如http://localhost:8082
spring:
cloud:
gateway:
routes:
- id: route-service
uri: http://localhost:8082
predicates:
- Path=/stock/**
6.3.2、斷言
斷言(predicates)
:Java8中的斷言函式,SpringCloud Gateway中的斷言函式型別是Spring5.0框架中的ServerWebExchange。斷言函式允許開發者去定義匹配Http request中的任何資訊,比如請求頭和引數等。
PredicateDefinition
斷言對應的類就是PredicateDefinition
@Validated
public class PredicateDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}
SpringCloudGateway中支援的斷言型別有很多,這些斷言的實現都是由spring cloud gateway的斷言工廠來實現的:
說明 | 示例 | |
---|---|---|
After | 是某個時間點後的請求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某個時間點之前的請求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某兩個時間點之前的請求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 請求必須包含某些cookie | - Cookie=chocolate, ch.p |
Header | 請求必須包含某些header | - Header=X-Request-Id, \d+ |
Host | 請求必須是訪問某個host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 請求方式必須是指定方式 | - Method=GET,POST |
Path | 請求路徑必須符合指定規則 | - Path=/red/{segment},/blue/** |
Query | 請求引數必須包含指定引數 | - Query=name, Jack或者- Query=name |
RemoteAddr | 請求者的ip必須是指定範圍 | - RemoteAddr=192.168.1.1/24 |
weight | 權重處理 |
此型別的斷言根據時間做判斷,主要有三個:
AfterRoutePredicateFactory: 接收一個日期引數,判斷請求日期是否晚於指定日期
BeforeRoutePredicateFactory: 接收一個日期引數,判斷請求日期是否早於指定日期
BetweenRoutePredicateFactory: 接收兩個日期引數,判斷請求日期是否在指定時間段內
時間的格式可以透過ZonedDateTime.now()來獲取
基於遠端地址的斷言工廠
RemoteAddrRoutePredicateFactory:接收一個IP地址段,判斷請求主機地址是否在地址段中
基於Cookie的斷言工廠
CookieRoutePredicateFactory:接收兩個引數,cookie 名字和一個正規表示式。 判斷請求
cookie是否具有給定名稱且值與正規表示式匹配。
基於Header的斷言工廠
HeaderRoutePredicateFactory:接收兩個引數,標題名稱和正規表示式。 判斷請求Header是否具有給定名稱且值與正規表示式匹配。
基於Host的斷言工廠
HostRoutePredicateFactory:接收一個引數,主機名模式。判斷請求的Host是否滿足匹配規則。
基於Method請求方法的斷言工廠
MethodRoutePredicateFactory:接收一個引數,判斷請求型別是否跟指定的型別匹配。
基於Path請求路徑的斷言工廠
PathRoutePredicateFactory:接收一個引數,判斷請求的URI部分是否滿足路徑規則。
基於Query請求引數的斷言工廠
QueryRoutePredicateFactory :接收兩個引數,請求param和正規表示式, 判斷請求引數是否具有給定名稱且值與正規表示式匹配。
自定義路由斷言工廠需要繼承 AbstractRoutePredicateFactory 類,重寫 apply 方法的邏輯。在 apply 方法中可以透過 exchange.getRequest() 拿到 ServerHttpRequest 物件,從而可以獲取到請求的引數、請求方式、請求頭等資訊。
1、 必須spring元件 bean
2、 類必須加上RoutePredicateFactory作為結尾
3、 必須繼承AbstractRoutePredicateFactory
4、 必須宣告靜態內部類 宣告屬性來接收 配置檔案中對應的斷言的資訊
5、 需要結合shortcutFieldOrder進行繫結
6、 透過apply進行邏輯判斷 true就是匹配成功 false匹配失敗
定義一個斷言工廠
@Slf4j
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
log.info("呼叫CheckAuthRoutePredicateFactory" +config.getName());
if(config.getName().equals("java-coder")){
return true;
}
return false;
}
};
}
/**
* Returns hints about the number of args and the order for shortcut parsing.
* 返回有關引數數量和快捷方式分析順序的提示。
*
* @return the list of hints
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
/**
* 用於接收配置資訊
*/
public static class Config{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
在配置檔案中使用自定義的過濾器工廠
spring:
cloud:
gateway:
routes:
- id: route-service
uri: http://localhost:8082
predicates:
- Path=/stock/**
- CheckAuth=xushu #此時就在配置檔案中使用了自定義的斷言工廠
6.3.4、過濾器
過濾器(Filter)
:SpringCloud Gateway中的filter分為閘道器過濾器 Gateway FilIer和全域性過濾器Global Filter。內建Filter都實現GatewayFilter介面。
GateFilter
:預設是不生效的,要配置到特定的路由下才會針對特定的路由生效。如果想要GateFilter對所有的路由都生效,需要把GateFilter配置在default-filters
下面。
GlobalFilter
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
內建過濾器
SpringCloud Gateway中內建了很多的過濾器工廠,我們透過一些過濾器工廠可以進行一些業務邏輯處理器,比如新增剔除響應頭,新增去除引數等
說明 | |
---|---|
AddRequestHeader | 給當前請求新增一個請求頭 |
AddRequestParameter | 為原始請求新增請求引數 |
AddResponseHeader | 給響應結果中新增一個響應頭 |
DedupeResponseHeader | 去掉重複請求頭 |
Spring Cloud CircuitBreaker | 斷路器 |
FallbackHeaders | 新增熔斷後的異常資訊到請求頭 |
MapRequestHeader | 將上游請求頭的值賦值到下游請求頭 |
PrefixPath | 匹配的路由新增字首 |
PreserveHostHeader | 保留原請求頭 |
RequestRateLimiter | 限制請求的流量 |
RedirectTo | 重定向 |
RemoveRequestHeader | 移除請求中的一個請求頭 |
RemoveResponseHeader | 從響應結果中移除有一個響應頭 |
RemoveRequestParameter | 移除請求引數 |
RewritePath | 重寫路徑 |
RewriteLocationResponseHeader | 重寫響應頭中Location的值 |
RewriteResponseHeader | 重寫響應頭 |
SaveSession | 向下遊轉發請求前前置執行WebSession::save的操作 |
SecureHeaders | 禁用預設值 |
SetPath | 設定路徑 |
SetRequestHeader | 重置請求頭 |
SetResponseHeader | 修改響應頭 |
SetStatus | 修改響應的狀態碼 |
StripPrefix | 對指定數量的路徑字首進行去除 |
Retry | 重試 |
RequestSize | 請求大小大於限制時,限制請求到達下游服務 |
SetRequestHostHeader | 重置請求頭值 |
Modify a Request Body | 修改請求體內容 |
Modify a Response Body | 修改響應體內容 |
Relay | 將 OAuth2 訪問令牌向下遊轉發到它所代理的服務 |
CacheRequestBody |
自定義區域性過濾器
自定義GatewayFilter
不是直接實現GatewayFilter
,而是實現AbstractGatewayFilterFactory
。
1、繼承AbstractGatewayFilterFactory
2、自定義名稱必須要以GatewayFilterFactory結尾並交給spring管理。
@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> {
public static final String KEY="key";
public static final String VALUE="value";
public CheckAuthGatewayFilterFactory() {
super(CheckAuthGatewayFilterFactory.Config.class);
}
/**
* 配置用於接收配置檔案中值的變數
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY, VALUE);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getRawPath();
log.info("請求路徑:{}",path);
log.info("{}={}",config.getKey(),config.getValue());
return chain.filter(exchange.mutate().request(request).build());
}
};
}
/**
* 自定義配置屬性,成員變數名稱很重要,下面會用到
*/
@Data
public static class Config {
private String key;
private String value;
}
}
在配置檔案中可以配置
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: stock-service
filters:
- CheckAuth=age,15
uri: lb://stock-service
predicates:
- Path=/stock/**
自定義全域性全域性過濾器
自定義全域性過濾器,直接實現GlobalFilter
介面就行,而且也無法設定動態引數。Ordered
是spring的核心介面,用於來對bean進行排序。
@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 編寫過濾器邏輯
System.out.println("未登入,無法訪問");
// 放行
// return chain.filter(exchange);
// 攔截
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(401);
return response.setComplete();
}
@Override
public int getOrder() {
// 過濾器執行順序,值越小,優先順序越高
return 0;
}
}
常用的一些全域性過濾器如下
-
Gateway Client向Gateway Server傳送請求
-
請求首先會被HttpWebHandlerAdapter進行提取組裝成閘道器上下文
-
然後閘道器的上下文會傳遞到DispatcherHandler,它負責將請求分發給RoutePredicateHandlerMapping
-
RoutePredicateHandlerMapping負責路由查詢,並根據路由斷言判斷路由是否可用
-
如果過斷言成功,由FilteringWebHandler建立過濾器鏈並呼叫
-
如圖所示:
-
客戶端請求進入閘道器後由
HandlerMapping
對請求做判斷,找到與當前請求匹配的路由規則(Route
),然後將請求交給WebHandler
去處理。 -
WebHandler
則會載入當前路由下需要執行的過濾器鏈(Filter chain
),然後按照順序逐一執行過濾器(後面稱為Filter
)。 -
圖中
Filter
被虛線分為左右兩部分,是因為Filter
內部的邏輯分為pre
和post
兩部分,分別會在請求路由到微服務之前和之後被執行。 -
只有所有
Filter
的pre
邏輯都依次順序執行透過後,請求才會被路由到微服務。 -
微服務返回結果後,再倒序執行
Filter
的post
邏輯。 -
最終把響應結果返回。
如圖中所示,最終請求轉發是有一個名為NettyRoutingFilter
的過濾器來執行的,而且這個過濾器是整個過濾器鏈中順序最靠後的一個。如果我們能夠定義一個過濾器,在其中實現登入校驗邏輯,並且將過濾器執行順序定義到NettyRoutingFilter
之前,這就符合我們的需求了!
登入校驗的思路:
1、登入請求先到閘道器,閘道器把登入請求轉發到user-service,user-service完成登入操作並生成token返回給前端
2、使用者請求到閘道器,閘道器對使用者請求進行校驗,判斷使用者是否登入。
(1)如果沒有登入則報錯
(2)如果登入了,則對token進行解析,然後把解析出來的使用者資訊新增到請求頭中,然後把路由轉發到業務微服務中。
3、微服務之間的呼叫是不走閘道器,此時也會有校驗,需要把請求的token也加入到服務之間呼叫的請求頭裡面
第一步使用者登入
此程式碼寫在spring-cloud-user服務裡面
/**
* 模擬使用者登入,登入賬號為zhangsan,密碼為123456
* @param name
* @param password
* @return
* @throws Exception
*/
@RequestMapping("/login")
public String login(String name,String password) throws Exception {
System.out.println("使用者名稱:"+name+",password:"+password);
if("zhangsan".equals(name)&&"123456".equals(password)){
String key=RedisPrefixConstants.USER_TOKEN_PREFIX+":"+name;
String token = EncryptionUtils.encrypt(name, EncryptionUtils.SECRET_KEY);
// 登入成功,用金鑰把登入的賬戶加密,生成的密串返回給前端(類似jwt), 並把使用者資訊存入到redis中
redisTemplate.opsForValue().set(key,name,30, TimeUnit.MINUTES);
return token;
}
throw new RuntimeException("登入失敗");
}
第二步gateway攔截器
此程式碼寫在spring-cloud-gateway裡面
在閘道器定義一個全域性攔截器,用來校驗使用者是否登入
package com.java.coder.gateway;
import com.java.coder.common.config.AuthProperties;
import com.java.coder.common.constants.RedisPrefixConstants;
import com.java.coder.common.utils.EncryptionUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@Slf4j
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private RedisTemplate<String,Object> redisTemplate;
private final AuthProperties authProperties;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 2.判斷是否不需要攔截
if(isExclude(request.getPath().toString())){
// 無需攔截,直接放行
return chain.filter(exchange);
}
List<String> list = request.getHeaders().get("token");
// 既不是白名單路徑,又沒有token,則禁止訪問
if(list==null||list.size()==0){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// 對token進行解密,如果解密不成功,則禁止訪問
String token = list.get(0);
String name = null;
try{
name=EncryptionUtils.decrypt(token, EncryptionUtils.SECRET_KEY);
}catch (Exception e){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// 把解密出來的賬號資訊放入請求頭中
String finalName = name;
exchange.mutate().request(b->b.header("user-info", finalName)).build();
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 過濾器執行順序,值越小,優先順序越高
return 0;
}
private boolean isExclude(String antPath) {
for (String pathPattern : authProperties.getExcludePaths()) {
if(antPathMatcher.match(pathPattern, antPath)){
return true;
}
}
return false;
}
}
第三步獲取當前登入使用者的工具類
此程式碼寫在spring-cloud-common
@Component
public class UserUtil {
private static RedisTemplate<String,String> redisTemplate;
@Autowired
public UserUtil(RedisTemplate<String,String> redisTemplate){
UserUtil.redisTemplate=redisTemplate;
}
public static String getCurrentUser(){
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
String name = request.getHeader("user-info");
String key= RedisPrefixConstants.USER_TOKEN_PREFIX+":"+name;
return redisTemplate.opsForValue().get(key);
}
}
因此類在spring-cloud-common裡面,spring-cloud-user、spring-cloud-stock等服務無法掃描到。此時有兩種解決辦法。
第一種方式: 利用spi機制
在spring.factories裡面配置
第二種方式: 利用SpringBootApplication註解裡面的scanBasePackages來指定UserUtil所在的包的路徑
因為我們在spring-cloud-common中定義了一個配置Redis的配置類,有可能出現注入RedisTemplate衝突的情況。
此時的解決方案是加一個@AutoConfigureBefore註解
因此spring-boot-starter-data-redis中的自動配置類是RedisAutoConfiguration,這個類在注入RedisTemplate時用了一個@ConditionalOnMissingBean註解,只有當spring容器中不存在名稱為redisTemplate的Bean時才會注入。因此當我們的Redis配置先於RedisAutoConfiguration配置類執行,那麼RedisAutoConfiguration配置類就不再會注入RedisTemplate。
原文連結:https://blog.csdn.net/m0_51681531/article/details/130507029
https://blog.csdn.net/m0_51681531/article/details/130507029
第四步驗證
我們呼叫stock-service服務,如果不帶上token,且/stock/getStock不是白名單裡面的路徑,則會返回403狀態碼
如果帶上之前登入介面返回的token,則可以順利呼叫