日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

springcloud api-gateway详解

發布時間:2025/3/19 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 springcloud api-gateway详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

網關

api-gateway

api-gateway是一款輕量級、高性能、易擴展的基于zuul的網關產品,提供API的統一管理服務、涵蓋API發布、管理、運維的全生命周期管理。對內輔助用戶簡單、快速、低成本、低風險的實現微服務聚合、前后端分離、系統集成等功能;對外面向合作伙伴、開發者開放服務。通過使用API-Gateway,我們能快速幫助用戶實現傳統ESB面臨的主要場景,又能滿足新型業務場景(移動應用等)所需的高性能、安全、可靠等要求。

通用網關設計

軟負載ZUUL

網關活動圖

api-gateway在項目中的位置

api gateway作用

  • 簡化客戶端調用復雜度
    在微服務架構模式下后端服務的實例數一般是動態的,對于客戶端而言,很難發現動態改變的服務實例的訪問地址信息。因此在基于微服務的項目中為了簡化前端的調用邏輯,通常會引入API Gateway作為輕量級網關,同時API Gateway中也會實現相關的認證邏輯從而簡化內部服務之間相互調用的復雜度。
  • 數據裁剪以及聚合
    通常而言不同的客戶端在顯示時對于數據的需求是不一致的,比如手機端或者Web端又或者在低延遲的網絡環境或者高延遲的網絡環境。因此為了優化客戶端的使用體驗,API Gateway可以對通用性的響應數據進行裁剪以適應不同客戶端的使用需求,同時還可以將多個API調用邏輯進行聚合,從而減少客戶端的請求數,優化客戶端用戶體驗。
  • 多渠道支持
    當然我們還可以針對不同的渠道和客戶端提供不同的API Gateway,對于該模式的使用由另外一個大家熟知的方式叫Backend for front-end,在Backend for front-end模式當中,我們可以針對不同的客戶端分別創建其BFF。
  • 遺留系統的微服務改造
    對于遺留系統而言進行微服務改造通常是由于原有的系統存在或多或少的問題,比如技術債務,代碼質量,可維護性,可擴展性等等。API Gateway的模式同樣適用于這一類遺留系統的改造,通過微服務化的改造逐步實現對原有系統中的問題的修復,從而提升對于原有業務相應力的提升。通過引入抽象層,逐步使用新的實現替換舊的實現。

    在Spring Cloud體系中,Spring Cloud Zuul就是提供負載均衡,反向代理,權限認證的一個API Gateway。

api-gateway代碼分析

開啟ZUUL

Zuul提供的功能

  • 認證鑒權-可以識別訪問資源的每一個請求,拒絕不滿足的請求
  • 監控埋點,跟蹤有意義的數據并統計,以便生成有意義的生產視圖
  • 熔斷限流,為每一個請求分配容量,并丟棄超過限制的請求
  • 抗壓設計,線程池隔離增大并發能力

網關的2層超時調優

hystrix ribbon活動圖

hystrix ribbon配置

#設置最大容錯超時時間 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 90000 #設置最大超時時間 ribbon: eager-load:enabled: trueServerListRefreshInterval: 10 #刷新服務列表源的間隔時間httpclient:enabled: falseokhttp:enabled: true ReadTimeout: 90000 ConnectTimeout: 90000 OkToRetryOnAllOperations: trueMaxAutoRetries: 1MaxAutoRetriesNextServer: 1

源碼分析

zuul內部代碼

類圖

參考:https://www.jianshu.com/p/295e51bc1518

zuul基于eureka的服務發現路由

路由注冊相關邏輯

org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator

啟動時配置mapping等信息

zuul轉發邏輯

以訪問http://127.0.0.1:9200/api-user/users-anon/login?username=admin為例

  • 通過/api-user/users-anon/login查映射表
  • 找到ZuulRoute映射關系
  • 執行pre過濾器org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter處理是否增加請求頭等信息
  • 執行route過濾器
  • org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter

總結

  • pre:這種過濾器在請求被轉發之前調用,一般用來實現身份驗證等
  • routing:這種路由是用來路由到不同的后端服務的,底層可以使用httpclient或者ribbon請求微服務
  • post:當請求轉發到微服務以后,會調用當前類型的過濾器。通常用來為響應天啊及標椎的HTTP Header、收集統計信息,等
  • error:當發生錯誤是執行的過濾器

網關自定義過濾器


  • brave.servlet.TracingFilter :生成traceId
  • com.open.capacity.common.filter.TraceContextFilter:傳遞traceId
  • com.open.capacity.client.filter.AccessFilter:傳遞token
  • com.open.capacity.client.filter.RequestStatFilter::傳遞traceId
  • com.open.capacity.client.filter.ResponseStatFilter:響應頭增加traceId

基于阿波羅配置中心的動態路由

參考代碼:https://gitee.com/owenwangwen/config-center/tree/master/apollo-gateway

阿波羅官方已吸收此案例在github可下載

https://github.com/ctripcorp/apollo-use-cases/tree/master/spring-cloud-zuul/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/zuul

  • 創建項目
  • 創建配置
  • 項目綁定配置

api-gateway 構建資源服務器

<!-- 資源服務器 --><dependency><groupId>com.open.capacity</groupId><artifactId>uaa-client-spring-boot-starter</artifactId></dependency>

uaa-client-spring-boot-starter源碼分析

網關認證處理流程圖

網關白名單

security:oauth2:ignored: /test163/** , /api-auth/** , /doc.html ,/test111 ,/api-user/users-anon/login, /api-user/users/save, /user-center/users-anon/login,/document.html,**/v2/api-docs,/oauth/** ,/login.html ,/user/login,/**/**.css ,/**/**.js ,/getVersiontoken:store:type: redis

網關統一異常

認證處理核心代碼

public Authentication authenticate(Authentication authentication) throws AuthenticationException {if (authentication == null) {throw new InvalidTokenException("Invalid token (token not found)");}String token = (String) authentication.getPrincipal();OAuth2Authentication auth = tokenServices.loadAuthentication(token);if (auth == null) {throw new InvalidTokenException("Invalid token: " + token);}Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");}checkClientDetails(auth);if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();// Guard against a cached copy of the same detailsif (!details.equals(auth.getDetails())) {// Preserve the authentication details from the one loaded by token servicesdetails.setDecodedDetails(auth.getDetails());}}auth.setDetails(authentication.getDetails());auth.setAuthenticated(true);return auth;

授權流程

啟用授權

@Beanpublic OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();expressionHandler.setApplicationContext(applicationContext);return expressionHandler;}

網關認證授權總結

  • 訪問者(Accessor)需要訪問某個資源(Resource)是這個場景最原始的需求,但并不是誰都可以訪問資源,也不是任何資源都允許任何人來訪問,所以中間我們要加入一些檢查和防護
  • 在訪問資源的所經之路上,可能遇到細菌,病毒,不管怎么樣,對于要防護的資源來說最好的方法就是設關卡點,對于上圖的FilterSecurityInvation,MethodIncation,Jointpoint,這些在spring security oauth中統稱SecuredObjects
  • 我們知道在哪里設置關卡點最合適,下一步就是設置關卡,對應FileSecurityInterceptor,MethodSecurityInterceptor,AspectSecurityInterceptor,
    這些關卡統一的抽象類是AbstractSecurityInterceptor
  • 有關卡點,關卡了以后,到底誰該攔截誰不應該呢,spring security oauth中由 AccessDecisionManager控制
  • 最后一個問題,這個誰怎么定義,我們總得知道當前這個訪問者是誰才能告訴AccessDecisionManager攔截還是放行,在spring security oauth框架中AuthenticationManager將解決訪問者身份認證問題,只有確定你在冊了,才可以給授權訪問。AuthenticationManager,AccessDecisionManager,AbstractSecurityInterceptor屬于spring security框架的基礎鐵三角。
  • 有了以上骨架,真正執行防護任務的其實是SecurityFilterChain中定于的一系列Filter,其中ExceptionTranslationFilter,它負責接待或者送客,如果訪問者來訪,對方沒有報上名來,那么,它就會讓訪客去登記認證(找AuthenticationManager做認證),如果對方報上名了,但認證失敗,那么請重新認證送客,送客的方式是拋出相應的Exception,所以名字叫做ExceptionTranslationFilter。
  • 最后,這個filter序列中可能不滿足我們的需求,比如增加驗證碼,所以我們需要在其中穿插自己的Filter實現類,為定制和擴展Spring Security Oauth的防護體系。
  • spring security內置的filter序列
  • 執行過濾鏈

網關api權限設計


相同用戶,不同應用的權限隔離
客戶端模式 :
客戶端A 申請的token ,可以訪問/api-user/menu/current ,
客戶端B 申請的token,不讓訪問/api-user/menu/current
密碼模式:
客戶端模式 :
客戶端A admin用戶 申請的token ,可以訪問/api-user/menu/current ,
客戶端B admin用戶 申請的token,不讓訪問/api-user/menu/current

參考issue:https://gitee.com/owenwangwen/open-capacity-platform/issues/IRG23

網關引依賴

網關是否開啟基于應用隔離,代碼注釋了,只是基于token的合法性校驗,按建議開啟是否啟用api接口服務權限
OpenAuthorizeConfigManager

游樂場買了通票,有些地方可以隨便玩,有些地方另外
單獨校驗買票
config.anyRequest().authenticated() ;
//這種通票,token校驗正確訪問
config.anyRequest().access("@rbacService.hasPermission(request, authentication)"); //這種另外
單獨校驗,適用于網關對api權限校驗


通過clientID隔離服務權限

通過應用分配服務權限

網關hystrix-dashboard

api-gateway 應用訪問次數控制

oauth_client_details 增加

字段備注
if_limit是否需要控制訪問次數
limit_count訪問閥值

auth-server 啟動

redis存儲結構

加載oauth_client_details 到redis

應用訪問次數控制過濾器

/*** Created by owen on 2017/9/10. 根據應用 url 限流 oauth_client_details if_limit 限流開關* limit_count 閾值*/ @Component public class RateLimitFilter extends ZuulFilter {private static Logger logger = LoggerFactory.getLogger(RateLimitFilter.class);private ThreadLocal<Result> error_info = new ThreadLocal<Result>();@Autowiredprivate RedisLimiterUtils redisLimiterUtils;@Autowiredprivate ObjectMapper objectMapper;@ResourceSysClientServiceImpl sysClientServiceImpl;@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {try {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (!checkLimit(request)) {logger.error("too many requests!");error_info.set(Result.failedWith(null, 429, "too many requests!"));serverResponse(ctx, 429);return null;}} catch (Exception e) {e.printStackTrace();}return null;}/**** 統一禁用輸出* * @param ctx* @param ret_message* 輸出消息* @param http_code* 返回碼*/public void serverResponse(RequestContext ctx, int http_code) {try {ctx.setSendZuulResponse(false);outputChineseByOutputStream(ctx.getResponse(), error_info);ctx.setResponseStatusCode(http_code);} catch (IOException e) {StackTraceElement stackTraceElement= e.getStackTrace()[0];logger.error("serverResponse:" + "---|Exception:" +stackTraceElement.getLineNumber()+"----"+ e.getMessage());}}/*** 使用OutputStream流輸出中文* * @param request* @param response* @throws IOException*/public void outputChineseByOutputStream(HttpServletResponse response, ThreadLocal<Result> data) throws IOException {/*** 使用OutputStream輸出中文注意問題: 在服務器端,數據是以哪個碼表輸出的,那么就要控制客戶端瀏覽器以相應的碼表打開,* 比如:outputStream.write("中國".getBytes("UTF-8"));//使用OutputStream流向客戶端瀏覽器輸出中文,以UTF-8的編碼進行輸出* 此時就要控制客戶端瀏覽器以UTF-8的編碼打開,否則顯示的時候就會出現中文亂碼,那么在服務器端如何控制客戶端瀏覽器以以UTF-8的編碼顯示數據呢?* 可以通過設置響應頭控制瀏覽器的行為,例如: response.setHeader("content-type",* "text/html;charset=UTF-8");//通過設置響應頭控制瀏覽器以UTF-8的編碼顯示數據*/OutputStream outputStream = response.getOutputStream();// 獲取OutputStream輸出流response.setHeader("content-type", "application/json;charset=UTF-8");// 通過設置響應頭控制瀏覽器以UTF-8的編碼顯示數據,如果不加這句話,那么瀏覽器顯示的將是亂碼/*** data.getBytes()是一個將字符轉換成字節數組的過程,這個過程中一定會去查碼表,* 如果是中文的操作系統環境,默認就是查找查GB2312的碼表, 將字符轉換成字節數組的過程就是將中文字符轉換成GB2312的碼表上對應的數字* 比如: "中"在GB2312的碼表上對應的數字是98 "國"在GB2312的碼表上對應的數字是99*//*** getBytes()方法如果不帶參數,那么就會根據操作系統的語言環境來選擇轉換碼表,如果是中文操作系統,那么就使用GB2312的碼表*/String msg = objectMapper.writeValueAsString(data.get());byte[] dataByteArr = msg.getBytes("UTF-8");// 將字符轉換成字節數組,指定以UTF-8編碼進行轉換outputStream.write(dataByteArr);// 使用OutputStream流向客戶端輸出字節數組}public boolean checkLimit(HttpServletRequest request) {// 解決zuul token傳遞問題Authentication user = SecurityContextHolder.getContext().getAuthentication();if (user != null) {if (user instanceof OAuth2Authentication) {try {OAuth2Authentication athentication = (OAuth2Authentication) user;OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) athentication.getDetails();String clientId = athentication.getOAuth2Request().getClientId();Map client = sysClientServiceImpl.getClient(clientId);if(client!=null){String flag = String.valueOf(client.get("if_limit") ) ;if("1".equals(flag)){String accessLimitCount = String.valueOf(client.get("limit_count") );if (!accessLimitCount.isEmpty()) {Result result = redisLimiterUtils.rateLimitOfDay(clientId, request.getRequestURI(),Long.parseLong(accessLimitCount));if (-1 == result.getResp_code()) {logger.error("token:" + details.getTokenValue() + result.getResp_msg());// ((ResultMsg)// this.error_info.get()).setMsg("clientid:" +// client_id + ":token:" + accessToken + ":" +// result.getMsg());// ((ResultMsg) this.error_info.get()).setCode(401);return false;}}}}} catch (Exception e) {StackTraceElement stackTraceElement= e.getStackTrace()[0];logger.error("checkLimit:" + "---|Exception:" +stackTraceElement.getLineNumber()+"----"+ e.getMessage());}}}return true;} }

RedisLimiterUtils核心類

@Component public class RedisLimiterUtils {public static final String API_WEB_TIME_KEY = "time_key:";public static final String API_WEB_COUNTER_KEY = "counter_key:";private static final String EXCEEDS_LIMIT = "規定的時間內超出了訪問的限制!";private static Logger logger = LoggerFactory.getLogger(RedisLimiterUtils.class);@ResourceRedisTemplate<Object, Object> redisTemplate;@Resource(name = "stringRedisTemplate")ValueOperations<String, String> ops;@Resource(name = "redisTemplate")ValueOperations<Object, Object> objOps;@ResourceRedisUtil redisUtil;public Result IpRateLimiter(String ip, int limit, int timeout) {String identifier = UUID.randomUUID().toString();String time_key = "time_key:ip:" + ip;String counter_key = "counter_key:ip:" + ip;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調用次數:" + this.ops.get(counter_key) );}public Result clientRateLimiter(String clientid, int limit, int timeout) {String identifier = UUID.randomUUID().toString();String time_key = "time_key:clientid:" + clientid;String counter_key = "counter_key:clientid:" + clientid;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調用次數:" + this.ops.get(counter_key) );}public Result urlRateLimiter(String path, int limit, int timeout) {String identifier = UUID.randomUUID().toString();String time_key = "time_key:path:" + path;String counter_key = "counter_key:path:" + path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調用次數:" + this.ops.get(counter_key) );}public Result clientPathRateLimiter(String clientid, String access_path, int limit, int timeout) {String identifier = UUID.randomUUID().toString();LocalDate today = LocalDate.now();String time_key = "time_key:clientid:" + clientid + ":path:" + access_path;String counter_key = "counter_key:clientid:" + clientid + ":path:" + access_path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調用次數:" + this.ops.get(counter_key) );}public Result rateLimitOfDay(String clientid, String access_path, long limit) {String identifier = UUID.randomUUID().toString();LocalDate today = LocalDate.now();String time_key = "time_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;String counter_key = "counter_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {//當天首次訪問,初始化訪問計數=0,有效期24hredisUtil.set(time_key, identifier, 24 * 60 * 60);redisUtil.set(counter_key, 0);}//累加訪問次數, 超出配置的limit則返回錯誤if (redisUtil.incr(counter_key, 1) > limit) {logger.info("日內超出了訪問的限制!");return Result.failedWith(null, -1, "日內超出了訪問的限制!");}return Result.succeedWith(null, 0, "調用總次數:" + this.ops.get(counter_key) );}public Result acquireRateLimiter(String clientid, String access_path, int limit, int timeout) {String identifier = UUID.randomUUID().toString();LocalDate today = LocalDate.now();String time_key = "time_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;String counter_key = "counter_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調用次數:" + this.ops.get(counter_key) );}public void save(String tokenType, String Token, int timeout) {redisUtil.set(tokenType, Token, timeout);}public String getToken(String tokenType) {return redisUtil.get(tokenType).toString();}public void saveObject(String key, Object obj, long timeout) {redisUtil.set(key, obj, timeout);}public void saveObject(String key, Object obj) {redisUtil.set(key, obj);}public Object getObject(String key) {return redisUtil.get(key);}public void removeObject(String key) {redisUtil.del(key);} }

生產軟負載NGINX構建ZUUL集群

pom核心依賴

總結

以上是生活随笔為你收集整理的springcloud api-gateway详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。