前言 在微服務架構中,1個系統會被拆分為了很多個微服務。 如果每1個微服務都直接對外暴露出來,讓用戶直接訪問這些微服務; 那麼如何對用戶的身份和許可權進行鑒定?如何對微服務中的訪問流量進行限流? 此時我們需要1個統一的入口(網關服務)以上問題將迎刃而解; 一、服務網關(Gateway)簡介 微服務的網 ...
前言
在微服務架構中,1個系統會被拆分為了很多個微服務。
如果每1個微服務都直接對外暴露出來,讓用戶直接訪問這些微服務;
那麼如何對用戶的身份和許可權進行鑒定?如何對微服務中的訪問流量進行限流?
此時我們需要1個統一的入口(網關服務)以上問題將迎刃而解;
-
客戶端多次請求不同的微服務,增加客戶端代碼或配置編寫的複雜性
-
認證複雜,每個服務都需要獨立認證。
-
存在跨域請求,在一定場景下處理相對複雜。
上面的這些問題可以藉助API網關來解決。所謂的API網關,就是指系統的統一入口。它封裝了應用程式的內部結構,為客戶端提供統一服務。
一些與業務本身功能無關的公共邏輯可以在這裡實現,諸如認證、鑒權、監控、路由轉發等等。
微服務網關的作用
- 提供了統一訪問入口,降低了服務受攻擊面
- 提供了統一跨域解決方案
- 提供了統一日誌記錄操作,可以進行統一監控
- 提供了統一許可權認證支持
- 提供了微服務限流功能,可以保護微服務,防止雪崩效應發生
1.創建1個api-gateway模塊
2.pom依賴
我們使用的網關產品為spring-cloud框架提供的gateway;
服務網關需要調用服務註冊中心(Nacos)獲取服務提供者的調用地址;
<dependencies> <!--引入gateway網關--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--Nacos服務發現依賴--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>
2.創建啟動類
package com.zhanggen.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
3.添加配置文件(application.yaml)
-
id,路由標識符,區別於其他 Route。
-
uri,路由指向的目的地 uri,即客戶端請求最終被轉發到的微服務。
-
predicate,斷言的作用是進行條件判斷,只有斷言都返回真,才會真正的執行路由。
-
filter
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由數組[路由 就是指定當請求滿足什麼條件的時候轉到哪個微服務]
- id: user-service-route # 當前路由的標識, 要求唯一
uri: lb://user-service # 請求要轉發到的地址
predicates: # 斷言(就是路由轉發要滿足的條件)
- Path=/user/** # 當請求路徑滿足Path指定的規則時,才進行路由轉發
4.測試網關路由轉發功能
啟動項目,並通過網關去訪問用戶微服務,現請求流程如下
- 匹配成功之後URL變成http://user-service/user/1
- GatWay調用客戶端負載均衡器(Ribin)去服務註冊中心(Nacos)拉取http://user-service/user/1對應的服務,進行負載均衡選擇
- 選擇出1個微服務之後http://user-service/user/1轉換成http://127.0.0.1:8081/user/1
- GatWay把用戶請求轉發到http://127.0.0.1:8081/user/1
1.基於Datetime類型的斷言
# AfterRoutePredicateFactory: 接收一個日期參數,判斷請求日期是否晚於指定日期
# BeforeRoutePredicateFactory: 接收一個日期參數,判斷請求日期是否早於指定日期
# BetweenRoutePredicateFactory: 接收兩個日期參數,判斷請求日期是否在指定時間段內
- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
2.基於遠程地址的斷言
# RemoteAddrRoutePredicateFactory:接收一個IP地址段,判斷請求主機地址是否在地址段中
- RemoteAddr=192.168.1.1/24
3.基於Cookie的斷言
# CookieRoutePredicateFactory:接收兩個參數,cookie 名字和一個正則表達式。 判斷請求cookie是否具有給定名稱且值與正則表達式匹配。
- Cookie=chocolate, ch.
4.基於Header的斷言
# HeaderRoutePredicateFactory:接收兩個參數,標題名稱和正則表達式。 判斷請求Header是否具有給定名稱且值與正則表達式匹配。
- Header=X-Request-Id, \d+
5.基於Host的斷言
# HostRoutePredicateFactory:接收一個參數,主機名模式。判斷請求的Host是否滿足匹配規則。
- Host=**.testhost.org
6.基於Path請求路徑的斷言
# PathRoutePredicateFactory:接收一個參數,判斷請求的URI部分是否滿足路徑規則。
- Path=/foo/{segment}
7.基於Query請求參數的斷言
# QueryRoutePredicateFactory :接收兩個參數,請求param和正則表達式, 判斷請求參數是否具有給定名稱且值與正則表達式匹配。
- Query=baz, ba.
8.使用
接下來我們驗證幾個內置斷言的使用:
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由數組[路由 就是指定當請求滿足什麼條件的時候轉到哪個微服務]
- id: user-service-route # 當前路由的標識, 要求唯一
uri: lb://user-service # 請求要轉發到的地址
predicates: # 斷言(就是路由轉發要滿足的條件)
- Path=/user/** # 當請求路徑滿足Path指定的規則時,才進行路由轉發
- Before=2019-11-28T00:00:00.000+08:00 # 限制請求時間在2019-11-28之前
- Method=POST # 限制請求方式為POST
Gateway也包含過濾器功能,網關服務的過濾器會對請求或響應進行攔截,完成一些通用操作。
1.過濾器執行時機
Gateway的過濾器中有2個執行時機:
2.過濾器類型
-
GatewayFilter:應用到單個路由或者一個分組的路由上
-
3.
作用 | 參數 | |
---|---|---|
AddRequestHeader | 為原始請求添加Header | Header的名稱及值 |
AddRequestParameter | 為原始請求添加請求參數 | 參數名稱及值 |
AddResponseHeader | 為原始響應添加Header | Header的名稱及值 |
DedupeResponseHeader | 剔除響應頭中重覆的值 | 需要去重的Header名稱及去重策略 |
Hystrix | 為路由引入Hystrix的斷路器保護 | HystrixCommand 的名稱 |
FallbackHeaders | 為fallbackUri的請求頭中添加具體的異常信息 | Header的名稱 |
PrefixPath | 為原始請求路徑添加首碼 | 首碼路徑 |
PreserveHostHeader | 為請求添加一個preserveHostHeader=true的屬性,路由過濾器會檢查該屬性以決定是否要發送原始的Host | 無 |
RequestRateLimiter | 用於對請求限流,限流演算法為令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 將原始請求重定向到指定的URL | http狀態碼及重定向的url |
RemoveHopByHopHeadersFilter | 為原始請求刪除IETF組織規定的一系列Header | 預設就會啟用,可以通過配置指定僅刪除哪些Header |
RemoveRequestHeader | 為原始請求刪除某個Header | Header名稱 |
RemoveResponseHeader | 為原始響應刪除某個Header | Header名稱 |
RewritePath | 重寫原始的請求路徑 | 原始路徑正則表達式以及重寫後路徑的正則表達式 |
RewriteResponseHeader | 重寫原始響應中的某個Header | Header名稱,值的正則表達式,重寫後的值 |
SaveSession | 在轉發請求之前,強制執行WebSession::save 操作 |
無 |
secureHeaders | 為原始響應添加一系列起安全作用的響應頭 | 無,支持修改這些安全響應頭的值 |
SetPath | 修改原始的請求路徑 | 修改後的路徑 |
SetResponseHeader | 修改原始響應中某個Header的值 | Header名稱,修改後的值 |
SetStatus | 修改原始響應的狀態碼 | HTTP 狀態碼,可以是數字,也可以是字元串 |
StripPrefix | 用於截斷原始請求的路徑 | 使用數字表示要截斷的路徑的數量 |
Retry | 針對不同的響應進行重試 | retries、statuses、methods、series |
RequestSize | 設置允許接收最大請求包的大小。如果請求包大小超過設置的值,則返回 413 Payload Too Large |
請求包大小,單位為位元組,預設值為5M |
ModifyRequestBody | 在轉發請求之前修改原始請求體內容 | 修改後的請求體內容 |
ModifyResponseBody | 修改原始響應體的內容 |
4.內置局部過濾器的使用
我們只需要把過濾器配置在微服務的配置文件中即可生效;
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由數組[路由 就是指定當請求滿足什麼條件的時候轉到哪個微服務]
- id: user-service-route # 當前路由的標識, 要求唯一
uri: lb://user-service # 請求要轉發到的地址
predicates: # 斷言(就是路由轉發要滿足的條件)
- Path=/user/** # 當請求路徑滿足Path指定的規則時,才進行路由轉發
filters:
- SetStatus=2000 # 修改返回狀態
6.
下麵,我們一起通過代碼的形式自定義一個過濾器,去完成統一的許可權校驗。
開發中的鑒權邏輯:
-
當客戶端第一次請求服務時,服務端對用戶進行信息認證(登錄)
-
認證通過,將用戶信息進行加密形成token,返回給客戶端,作為登錄憑證
-
以後每次請求,客戶端都攜帶認證的token
-
下麵的我們自定義一個GlobalFilter,去校驗所有請求的請求參數中是否包含“token”,如何不包含請求參數“token”則不轉發路由,否則執行正常的邏輯。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.zhanggen.gateway.auth; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; //全局認證過濾器 @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { //認證邏輯 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //請求參數數,獲取一個叫token的參數的值 String token = request.getParamter("token") String token = exchange.getRequest().getQueryParams().getFirst("token"); //判斷是否請求參數中攜帶了token if (StringUtils.isBlank(token)) { System.out.println("鑒權失敗"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//設置響應碼 resp.setStatus(401) return exchange.getResponse().setComplete();//返迴響應 } //調用chain.filter繼續向下游執行 return chain.filter(exchange); } //決定當前過濾器的執行級別, 數組越小,優先順序越高 @Override public int getOrder() { return 0; } }com.zhanggen.gateway.auth.AuthGlobalFilter
當過濾器的order值一樣時,局部過濾器 優先順序高於 全局過濾器;