目錄
一:統(tǒng)一網(wǎng)關(guān)Gateway
1. 為什么需要網(wǎng)關(guān)
2. gateway快速入門(mén)
3. 斷言工廠
4. 過(guò)濾器工廠
5. 全局過(guò)濾器
6. 跨域問(wèn)題
一:統(tǒng)一網(wǎng)關(guān)Gateway
前面我們已經(jīng)學(xué)習(xí)了注冊(cè)中心Eureka、Nacos和配置管理中心Nacos;但是此時(shí)存在很多安全的問(wèn)題,服務(wù)器擺在那里誰(shuí)都可以進(jìn)行訪問(wèn)!
1. 為什么需要網(wǎng)關(guān)
網(wǎng)關(guān)功能:
①身份認(rèn)證和權(quán)限校驗(yàn):微服務(wù)直接擺在那里允許任何人都可以訪問(wèn),不太安全;需要進(jìn)行身份驗(yàn)證,一切請(qǐng)求先到網(wǎng)關(guān)Gateway再到微服務(wù),驗(yàn)證過(guò)后在進(jìn)行放行!
②服務(wù)路由、負(fù)載均衡:放行過(guò)后,問(wèn)題又來(lái)了,當(dāng)用戶放松請(qǐng)求處理業(yè)務(wù)時(shí),網(wǎng)關(guān)肯定處理不了業(yè)務(wù),需要把請(qǐng)求給對(duì)應(yīng)的微服務(wù);但是需要判斷是發(fā)給order-service還是user-service進(jìn)行處理?每一個(gè)微服務(wù)后面肯定有很多實(shí)例,所以還需要進(jìn)行服務(wù)路由和負(fù)載均衡!
③請(qǐng)求限流:允許用戶的請(qǐng)求量,限量;是對(duì)微服務(wù)的一種保護(hù)機(jī)制!
網(wǎng)關(guān)的技術(shù)實(shí)現(xiàn):
在SpringCloud中網(wǎng)關(guān)的實(shí)現(xiàn)包括兩種:
①Gateway:SpringCloudGateway是基于Spring5中提供的WebFlux,屬于響應(yīng)式編程的實(shí)現(xiàn),具備更好的性能。
②Zuul:Zuul是基于Servlet的實(shí)現(xiàn),屬于阻塞式編程。
總結(jié)網(wǎng)關(guān)的作用:
①對(duì)用戶請(qǐng)求做身份認(rèn)證、權(quán)限校驗(yàn);?
②將用戶請(qǐng)求路由到微服務(wù),并實(shí)現(xiàn)負(fù)載均衡 ;
③對(duì)用戶請(qǐng)求做限流;
2. gateway快速入門(mén)
搭建網(wǎng)關(guān)服務(wù)的步驟:
第一步:創(chuàng)建新的module,引入SpringCloudGateway的依賴和nacos的服務(wù)發(fā)現(xiàn)依賴
注:這里需要Nacos依賴是因?yàn)橐惨丫W(wǎng)關(guān)Gateway也注入注冊(cè)中心Nacos里!
<!--網(wǎng)關(guān)依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服務(wù)發(fā)現(xiàn)依賴,把自己注入Nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步:服務(wù)的啟動(dòng)需要啟動(dòng)類
package cn.itcast.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
第三步:application.yml中編寫(xiě)路由規(guī)則配置及nacos地址
server:
port: 10010 # 服務(wù)端口
spring:
application:
name: gateway # 服務(wù)名稱
cloud:
nacos:
server-addr: localhost:8848 # nacos服務(wù)地址
gateway: # 服務(wù)路由配置
routes: # 表示規(guī)則
- id: userservice # 路由標(biāo)識(shí)
# uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址
uri: lb://user-service # 路由的目標(biāo)地址 lb就是負(fù)載均衡,后面跟服務(wù)名稱
predicates:
- Path=/user/** # 路徑斷言,判斷是否以/user開(kāi)頭
- id: orderservice
uri: lb://order-service
predicates:
- Path=/order/**
啟動(dòng)服務(wù),此時(shí)訪問(wèn)就不需要:http://localhost:8080/order/101?而是http://localhost:10010/order/101?這種形式
成功把把請(qǐng)求從網(wǎng)關(guān)路由到微服務(wù)!
原理剖析
①首先發(fā)起請(qǐng)求,端口是10010,而網(wǎng)關(guān)端口號(hào)也是10010,一定會(huì)進(jìn)入網(wǎng)關(guān)!網(wǎng)關(guān)無(wú)法處理業(yè)務(wù),只能基于路由規(guī)則進(jìn)行判斷(前面定義了兩個(gè)路由規(guī)則)。
②根據(jù)路由規(guī)則匹配到的是user-service,然后就可以找到nacos注冊(cè)中心進(jìn)行服務(wù)拉取,再去負(fù)載均衡挑一個(gè)。
網(wǎng)關(guān)搭建步驟:
1. 創(chuàng)建項(xiàng)目,引入nacos服務(wù)發(fā)現(xiàn)和gateway依賴;
2. 配置application.yml,包括服務(wù)基本信息、nacos地址、路由;
路由配置包括:
1. 路由id:路由的唯一標(biāo)示;
2. 路由目標(biāo)(uri):路由的目標(biāo)地址,http代表固定地址,lb代表根據(jù)服務(wù)名負(fù)載均衡;
3. 路由斷言(predicates):判斷路由的規(guī)則;
4. 路由過(guò)濾器(filters):對(duì)請(qǐng)求或響應(yīng)做處理;(后面會(huì)講)
3. 斷言工廠
網(wǎng)關(guān)路由可以配置的內(nèi)容包括:
①路由id:路由唯一標(biāo)示;
②uri:路由目的地,支持lb和http兩種;
③predicates:路由斷言,判斷請(qǐng)求是否符合要求,符合則轉(zhuǎn)發(fā)到路由目的地;
④filters:路由過(guò)濾器,處理請(qǐng)求或響應(yīng);
路由斷言工廠Route Predicate Factory
注:我們?cè)谂渲梦募袑?xiě)的斷言規(guī)則只是字符串,這些字符串會(huì)被路由斷言工廠Predicate Factory讀取并處理解析,轉(zhuǎn)變?yōu)槁酚膳袛嗟臈l件。例如:Path=/user/**是按照路徑匹配,這個(gè)規(guī)則是org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory類來(lái)處理的像這樣的斷言工廠在SpringCloudGateway還有十幾個(gè)!
Spring提供了11種基本的Predicate工廠:
名稱 |
說(shuō)明 |
示例 |
After |
是某個(gè)時(shí)間點(diǎn)后的請(qǐng)求 |
- After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before |
是某個(gè)時(shí)間點(diǎn)之前的請(qǐng)求 |
- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between |
是某兩個(gè)時(shí)間點(diǎn)之前的請(qǐng)求 |
- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie |
請(qǐng)求必須包含某些cookie |
- Cookie=chocolate, ch.p |
Header |
請(qǐng)求必須包含某些header |
- Header=X-Request-Id, \d+ |
Host |
請(qǐng)求必須是訪問(wèn)某個(gè)host(域名) |
- Host=**.somehost.org,**.anotherhost.org |
Method |
請(qǐng)求方式必須是指定方式 |
- Method=GET,POST |
Path |
請(qǐng)求路徑必須符合指定規(guī)則 |
- Path=/red/{segment}, /blue/** |
Query |
請(qǐng)求參數(shù)必須包含指定參數(shù) |
- Query=name, Jack或者- Query=name |
RemoteAddr |
請(qǐng)求者的ip必須是指定范圍 |
- RemoteAddr=192.168.1.1/24 |
Weight |
權(quán)重處理 |
?詳細(xì)的使用規(guī)則參考官網(wǎng):Spring Cloud Gateway
注:如果此時(shí)路由規(guī)則不符合,瀏覽器頁(yè)面包404錯(cuò)誤!
增加時(shí)間路由規(guī)則:給order-service增加在2023后訪問(wèn)才符合規(guī)則
predicates:
- Path=/order/**
- After=2031-01-20T17:42:47.789-07:00[America/Denver] # 表明在2023年后訪問(wèn)符合
執(zhí)行結(jié)果:?
1. PredicateFactory的作用是什么?
讀取用戶定義的斷言條件,對(duì)請(qǐng)求進(jìn)行解析并做出判斷。
2. Path=/user/**是什么含義?
對(duì)請(qǐng)求對(duì)路進(jìn)行解析,路徑是以/user開(kāi)頭的就認(rèn)為是符合的。
4. 過(guò)濾器工廠?
過(guò)濾器工廠 GatewayFilter
GatewayFilter是網(wǎng)關(guān)中提供的一種過(guò)濾器,可以對(duì)進(jìn)入網(wǎng)關(guān)的請(qǐng)求和微服務(wù)返回的響應(yīng)做處理!
Spring提供了31種不同的路由過(guò)濾器工廠。例如:
更詳細(xì)的可以參考官方網(wǎng)站:Spring Cloud Gateway
名稱 |
說(shuō)明 |
AddRequestHeader |
給當(dāng)前請(qǐng)求添加一個(gè)請(qǐng)求頭 |
RemoveRequestHeader |
移除請(qǐng)求中的一個(gè)請(qǐng)求頭 |
AddResponseHeader |
給響應(yīng)結(jié)果中添加一個(gè)響應(yīng)頭 |
RemoveResponseHeader |
從響應(yīng)結(jié)果中移除有一個(gè)響應(yīng)頭 |
RequestRateLimiter |
限制請(qǐng)求的流量 |
... |
案例:給所有進(jìn)入user-service的請(qǐng)求添加一個(gè)請(qǐng)求頭Truth=Itcast is freaking awesome!
注:key和value之間是以?逗號(hào) 的方式連接!
驗(yàn)證執(zhí)行結(jié)果:在UserController中使用@RequestHeader注解拿到請(qǐng)求頭信息
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth",required = false) String truth
) {
// 進(jìn)行打印
System.out.println("Truth: "+truth);
return userService.queryById(id);
}
思考:此時(shí)只是給某個(gè)微服務(wù)增加請(qǐng)求頭信息,那么如果是所有的微服務(wù)都添加呢?
注:使用默認(rèn)過(guò)濾器default-filter。配置的某一個(gè)微服務(wù)的過(guò)濾器,其filter在route的下面一級(jí);而全局過(guò)濾器default-filter是與route同級(jí)!
5. 全局過(guò)濾器
全局過(guò)濾器 GlobalFilter
全局過(guò)濾器的作用也是處理一切進(jìn)入網(wǎng)關(guān)的請(qǐng)求和微服務(wù)響應(yīng),與GatewayFilter的作用一樣!區(qū)別在于GatewayFilter通過(guò)配置定義,處理邏輯是固定的。而GlobalFilter的邏輯需要自己寫(xiě)代碼實(shí)現(xiàn)。
定義方式是實(shí)現(xiàn)GlobalFilter接口
exchange參數(shù):?請(qǐng)求上下文,里面可以獲取Request、Response等信息;
chain參數(shù):過(guò)濾器鏈,用來(lái)把請(qǐng)求委托給下一個(gè)過(guò)濾器,放行;
Mono<Void>: 返回標(biāo)示當(dāng)前過(guò)濾器業(yè)務(wù)結(jié)束;
package org.springframework.cloud.gateway.filter;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
需求:定義全局過(guò)濾器,攔截請(qǐng)求,判斷請(qǐng)求的參數(shù)是否滿足下面條件
參數(shù)中是否有authorization,authorization參數(shù)值是否為admin,如果同時(shí)滿足則放行,否則攔截!
在gateway啟動(dòng)類的同包下定義一個(gè)過(guò)濾器
①首先通過(guò)exchange參數(shù)獲取到request對(duì)象,調(diào)用request對(duì)象的getQueryParams方法獲取到所有的請(qǐng)求參數(shù)。然后從請(qǐng)求參數(shù)中通過(guò)authorization這個(gè)key獲取value值admin。如果這個(gè)值存在:就調(diào)用chain執(zhí)行鏈的filter方法,把exchange傳下去;如果這個(gè)值不存在:就通過(guò)exchange參數(shù)獲取到response對(duì)象,通過(guò)這個(gè)對(duì)象的setComplete方法進(jìn)行攔截。在攔截之前還可以通過(guò)通過(guò)response方法設(shè)置狀態(tài)碼,增加用戶的體驗(yàn)感!
②增加@Component注解組件掃描注解,納入Spring的管理。
③增加@Order注解,順序組件;將來(lái)可能會(huì)定義很對(duì)組件,這里是為了先執(zhí)行。還可以實(shí)現(xiàn)Ordered接口,重寫(xiě)getOrder方法進(jìn)行設(shè)置。
package cn.itcast.gateway;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
// @Order(-1)
@Component
public class AuthrizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 第一步:獲取所有參數(shù)
// 獲取request對(duì)象
ServerHttpRequest request = exchange.getRequest();
// 獲取所有請(qǐng)求參數(shù)
MultiValueMap<String, String> params = request.getQueryParams();
// 第二步:根據(jù)authorization參數(shù)獲取value值
String auth = params.getFirst("authorization");
// 第三步:判斷請(qǐng)求參數(shù)的值是不是等于admin
if ("admin".equals(auth)){
// 是,放行
return chain.filter(exchange);
}
// 不是,攔截
// 結(jié)束之前設(shè)置狀態(tài)碼
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 實(shí)現(xiàn)Ordered接口的方法也可以
@Override
public int getOrder() {
return -1;
}
}
進(jìn)行訪問(wèn):如果不增加參數(shù)就會(huì)報(bào)401錯(cuò)誤(未登錄錯(cuò)誤)
1.?全局過(guò)濾器的作用是什么?
對(duì)所有路由都生效的過(guò)濾器(這點(diǎn)和默認(rèn)過(guò)濾器default-filter效果相同),并且可以自定義處理邏輯,比較靈活;
2. 實(shí)現(xiàn)全局過(guò)濾器的步驟?
①實(shí)現(xiàn)GlobalFilter接口;
②添加@Order注解或?qū)崿F(xiàn)Ordered接口 和 添加組件掃描@Component注解;
③編寫(xiě)處理邏輯;
過(guò)濾器執(zhí)行順序
前面已經(jīng)講解了三個(gè)過(guò)濾器:路由過(guò)濾球、默認(rèn)的過(guò)濾器、全局過(guò)濾器;接下來(lái)就分析一下這三個(gè)過(guò)濾器的執(zhí)行順序!
注:請(qǐng)求進(jìn)入網(wǎng)關(guān)會(huì)碰到三類過(guò)濾器:當(dāng)前路由的過(guò)濾器、DefaultFilter、GlobalFilter請(qǐng)求路由后,會(huì)將當(dāng)前路由過(guò)濾器和DefaultFilter、GlobalFilter,合并到一個(gè)過(guò)濾器鏈(集合)中,排序后依次執(zhí)行每個(gè)過(guò)濾器
思考1:從目前來(lái)看,這三個(gè)過(guò)濾器不是同一種類型,怎么能放到同一個(gè)集合當(dāng)中呢?
1. 對(duì)于路由過(guò)濾器和默認(rèn)過(guò)濾器default-filter從配置文件來(lái)看配置方式相同:
其本質(zhì)上實(shí)際都是AddRequestHeaderGatewayFilterFactory對(duì)象!這個(gè)過(guò)濾器的工廠就會(huì)讀取配置文件生成一個(gè)真正的過(guò)濾器GatewayFilter;所以路由過(guò)濾器默認(rèn)過(guò)濾器都是同一類:GatewayFilter!
2. 在FilteringWebHandler類里面有一個(gè)FilteringWebHandler(過(guò)濾器適配器)這個(gè)類適配器實(shí)現(xiàn)了GatewayFilter接口,在適配器內(nèi)部又接收了一個(gè)全局過(guò)濾器參數(shù)GlobalFiter;通過(guò)適配器模式進(jìn)行傳參當(dāng)做GatewayFilter來(lái)使用,這樣就建立了聯(lián)系!
所以可以認(rèn)為這三種過(guò)濾器都是GatewayFilter類型,同一種類型就可以放到List集合當(dāng)中進(jìn)行排序!
思考2:這樣新的問(wèn)題就引出來(lái)了,怎么進(jìn)行排序呢?
①我們已經(jīng)知道,每一個(gè)過(guò)濾器都必須指定一個(gè)int類型的order值,order值越小,優(yōu)先級(jí)越高,執(zhí)行順序越靠前。
②對(duì)于全局過(guò)濾器GlobalFilter通過(guò)實(shí)現(xiàn)Ordered接口,或者添加@Order注解來(lái)指定order值,由我們自己指定。
③對(duì)于路由過(guò)濾器和默認(rèn)過(guò)濾器我們并沒(méi)有去指定順序!路由過(guò)濾器和默認(rèn)過(guò)濾器defaultFilter的order由Spring指定,默認(rèn)是按照聲明順序從1遞增。例如:
各排各的:?
④如果此時(shí)過(guò)濾器的order值都是1怎么辦呢?
當(dāng)過(guò)濾器的order值一樣時(shí),會(huì)按照 defaultFilter > 路由過(guò)濾器 > GlobalFilter的順序執(zhí)行。
詳情可以參考源碼:很清晰,可以自己看一下
①org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加載defaultFilters,然后再加載某個(gè)route的filters,然后合并。②org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法會(huì)加載全局過(guò)濾器,與前面的過(guò)濾器合并后根據(jù)order排序,組織過(guò)濾器鏈。
6. 跨域問(wèn)題
跨域問(wèn)題處理
在微服務(wù)當(dāng)中,所有的請(qǐng)求都要先經(jīng)過(guò)網(wǎng)關(guān),在到微服務(wù);這樣就不要在每個(gè)微服務(wù)進(jìn)行處理,只需要在網(wǎng)關(guān)中進(jìn)行處理!
跨域:域名不一致就是跨域,主要包括:
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
域名相同,端口不同:localhost:8080和localhost8081
跨域問(wèn)題:瀏覽器禁止請(qǐng)求的發(fā)起者與服務(wù)端發(fā)生跨域ajax請(qǐng)求,請(qǐng)求被瀏覽器攔截的問(wèn)題!
解決方案:CORS(瀏覽器去詢問(wèn)服務(wù)器的方式)
通過(guò)axios發(fā)送get請(qǐng)求,請(qǐng)求地址就是網(wǎng)關(guān)的地址
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<pre>
spring:
cloud:
gateway:
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請(qǐng)求被攔截問(wèn)題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網(wǎng)站的跨域請(qǐng)求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允許的跨域ajax的請(qǐng)求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請(qǐng)求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測(cè)的有效期
</pre>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.get("http://localhost:10010/user/1?authorization=admin")
.then(resp => console.log(resp.data))
.catch(err => console.log(err))
</script>
</html>
以8090端口進(jìn)行運(yùn)行,此時(shí)在控制臺(tái)就可以看到報(bào)錯(cuò)請(qǐng)求:
進(jìn)行配置文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-761663.html
server:
port: 10010 # 服務(wù)端口
spring:
application:
name: gateway # 服務(wù)名稱
cloud:
nacos:
server-addr: localhost:8848 # nacos服務(wù)地址
gateway: # 服務(wù)路由配置
routes:
- id: userservice # 路由標(biāo)識(shí)
# uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址
uri: lb://user-service # 路由的目標(biāo)地址 lb就是負(fù)載均衡,后面跟服務(wù)名稱
predicates:
- Path=/user/** # 路徑斷言,判斷是否以/user開(kāi)頭
# filters:
# - AddRequestHeader=Truth,Itcast is freaking awesome! # 注意key和value之間是以逗號(hào)隔開(kāi)
- id: orderservice
uri: lb://order-service
predicates:
- Path=/order/**
- Before=2031-01-20T17:42:47.789-07:00[America/Denver]
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awesome! # 注意key和value之間是以逗號(hào)隔開(kāi)
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請(qǐng)求被攔截問(wèn)題(防止CORS瀏覽器詢問(wèn)服務(wù)器攔截) corsConfigurations:
corsConfigurations:
'[/**]': # 攔截所有的請(qǐng)求
allowedOrigins: # 允許哪些網(wǎng)站的跨域請(qǐng)求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允許的跨域ajax的請(qǐng)求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請(qǐng)求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測(cè)的有效期,有效期內(nèi)直接放行
?重啟網(wǎng)關(guān),此時(shí)再次以8090端口發(fā)送請(qǐng)求就可以跨域訪問(wèn)啦!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-761663.html
到了這里,關(guān)于SpringCloud微服務(wù) 【實(shí)用篇】| 統(tǒng)一網(wǎng)關(guān)Gateway的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!