<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          快速搭建一個(gè)網(wǎng)關(guān)服務(wù),動(dòng)態(tài)路由、鑒權(quán)的流程,看完秒會(huì)(含流程圖)

          共 16979字,需瀏覽 34分鐘

           ·

          2022-07-09 09:55

          ?

          大家好,我是寶哥!

          ??

          前言

          本文記錄一下我是如何使用Gateway搭建網(wǎng)關(guān)服務(wù)及實(shí)現(xiàn)動(dòng)態(tài)路由的,幫助大家學(xué)習(xí)如何快速搭建一個(gè)網(wǎng)關(guān)服務(wù),了解路由相關(guān)配置,鑒權(quán)的流程及業(yè)務(wù)處理,有興趣的一定看到最后,非常適合沒接觸過網(wǎng)關(guān)服務(wù)的同學(xué)當(dāng)作入門教程。

          搭建服務(wù)

          框架

          SpringBoot 2.1

          <parent>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-parent</artifactId>
             <version>2.1.0.RELEASE</version>
          </parent>

          Spring-cloud-gateway-core

          <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-gateway-core</artifactId>
          </dependency>

          common-lang3

          <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
          </dependency>
          路由配置

          網(wǎng)關(guān)作為請(qǐng)求統(tǒng)一入口,路由就相當(dāng)于是每個(gè)業(yè)務(wù)系統(tǒng)的入口,通過路由規(guī)則則可以匹配到對(duì)應(yīng)微服務(wù)的入口,將請(qǐng)求命中到對(duì)應(yīng)的業(yè)務(wù)系統(tǒng)中

          server:
            port: 8080

          spring:
            cloud:
              gateway:
                enabled: true
                routes:
                - id: demo-server
                  uri: http://localhost:8081
                  predicates:
                  - Path=/demo-server/**
                  filters:
                    - StripPrefix= 1

          routes

          解讀配置
          • 現(xiàn)在有一個(gè)服務(wù)demo-server部署在本機(jī),地址和端口為127.0.0.1:8081,所以路由配置uri為http://localhost:8081
          • 使用網(wǎng)關(guān)服務(wù)路由到此服務(wù),predicates -Path=/demo-server/**,網(wǎng)關(guān)服務(wù)的端口為8080,啟動(dòng)網(wǎng)關(guān)服務(wù),訪問localhost:8080/demo-server,路由斷言就會(huì)將請(qǐng)求路由到demo-server
          • 直接訪問demo-server的接口localhost:8081/api/test,通過網(wǎng)關(guān)的訪問地址則為localhost:8080/demo-server/api/test,predicates配置將請(qǐng)求斷言到此路由,filters-StripPrefix=1代表將地址中/后的第一個(gè)截取,所以demo-server就截取掉了

          使用gateway通過配置文件即可完成路由的配置,非常方便,我們只要充分的了解配置項(xiàng)的含義及規(guī)則就可以了;但是這些配置如果要修改則需要重啟服務(wù),重啟網(wǎng)關(guān)服務(wù)會(huì)導(dǎo)致整個(gè)系統(tǒng)不可用,這一點(diǎn)是無法接受的,下面介紹如何通過Nacos實(shí)現(xiàn)動(dòng)態(tài)路由

          動(dòng)態(tài)路由

          使用nacos結(jié)合gateway-server實(shí)現(xiàn)動(dòng)態(tài)路由,我們需要先部署一個(gè)nacos服務(wù),可以使用docker部署或下載源碼在本地啟動(dòng),具體操作可以參考官方文檔即可

          Nacos配置

          groupId: 使用網(wǎng)關(guān)服務(wù)名稱即可

          dataId: routes

          配置格式:json

          [{
                "id""xxx-server",
                "order": 1, #優(yōu)先級(jí)
                "predicates": [{ #路由斷言
                    "args": {
                        "pattern""/xxx-server/**"
                    },
                    "name""Path"
                }],
                "filters":[{ #過濾規(guī)則
                    "args": {
                        "parts": 0 #k8s服務(wù)內(nèi)部訪問容器為http://xxx-server/xxx-server的話,配置0即可
                    },
                    "name""StripPrefix" #截取的開始索引
                }],
                "uri""http://localhost:8080/xxx-server" #目標(biāo)地址
            }]

          json格式配置項(xiàng)與yaml中對(duì)應(yīng),需要了解配置在json中的寫法

          關(guān)注公眾號(hào):Java后端編程,回復(fù):666領(lǐng)取資料 。

          比對(duì)一下json配置與yaml配置
          {
              "id":"demo-server",
              "predicates":[
                  {
                      "args":{
                          "pattern":"/demo-server/**"
                      },
                      "name":"Path"
                  }
              ],
              "filters":[
                  {
                      "args":{
                          "parts":1
                      },
                      "name":"StripPrefix"
                  }
              ],
              "uri":"http://localhost:8081"
          }
          spring:
            cloud:
              gateway:
                enabled: true
                routes:
                - id: demo-server
                  uri: http://localhost:8081
                  predicates:
                  - Path=/demo-server/**
                  filters:
                    - StripPrefix= 1
          代碼實(shí)現(xiàn)

          Nacos實(shí)現(xiàn)動(dòng)態(tài)路由的方式核心就是通過Nacos配置監(jiān)聽,配置發(fā)生改變后執(zhí)行網(wǎng)關(guān)相關(guān)api創(chuàng)建路由

          @Component
          public class NacosDynamicRouteService implements ApplicationEventPublisherAware {

              private static final Logger LOGGER = LoggerFactory.getLogger(NacosDynamicRouteService.class);

              @Autowired
              private RouteDefinitionWriter routeDefinitionWriter;

              private ApplicationEventPublisher applicationEventPublisher;

              /** 路由id */
              private static List<String> routeIds = Lists.newArrayList();

              /**
               * 監(jiān)聽nacos路由配置,動(dòng)態(tài)改變路由
               * @param configInfo
               */

              @NacosConfigListener(dataId = "routes", groupId = "gateway-server")
              public void routeConfigListener(String configInfo) {
                  clearRoute();
                  try {
                      List<RouteDefinition> gatewayRouteDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                      for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
                          addRoute(routeDefinition);
                      }
                      publish();
                      LOGGER.info("Dynamic Routing Publish Success");
                  } catch (Exception e) {
                      LOGGER.error(e.getMessage(), e);
                  }
                  
              }


              /**
               * 清空路由
               */

              private void clearRoute() {
                  for (String id : routeIds) {
                      routeDefinitionWriter.delete(Mono.just(id)).subscribe();
                  }
                  routeIds.clear();
              }

              @Override
              public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
                  this.applicationEventPublisher = applicationEventPublisher;
              }

              /**
               * 添加路由
               * 
               * @param definition
               */

              private void addRoute(RouteDefinition definition) {
                  try {
                      routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                      routeIds.add(definition.getId());
                  } catch (Exception e) {
                      LOGGER.error(e.getMessage(), e);
                  }
              }

              /**
               * 發(fā)布路由、使路由生效
               */

              private void publish() {
                  this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
              }
          }
          過濾器

          gateway提供GlobalFilter及Ordered兩個(gè)接口用來定義過濾器,我們自定義過濾器只需要實(shí)現(xiàn)這個(gè)兩個(gè)接口即可

          • GlobalFilter filter() 實(shí)現(xiàn)過濾器業(yè)務(wù)
          • Ordered getOrder() 定義過濾器執(zhí)行順序

          通常一個(gè)網(wǎng)關(guān)服務(wù)的過濾主要包含 鑒權(quán)(是否登錄、是否黑名單、是否免登錄接口...) 限流(ip限流等等)功能,我們今天簡(jiǎn)單介紹鑒權(quán)過濾器的流程實(shí)現(xiàn)

          鑒權(quán)過濾器

          需要實(shí)現(xiàn)鑒權(quán)過濾器,我們先得了解登錄及鑒權(quán)流程,如下圖所示

          由圖可知,我們鑒權(quán)過濾核心就是驗(yàn)證token是否有效,所以我們網(wǎng)關(guān)服務(wù)需要與業(yè)務(wù)系統(tǒng)在同一個(gè)redis庫(kù),先給網(wǎng)關(guān)添加redis依賴及配置

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
          </dependency>
          spring:
            redis:
              host: redis-server
              port: 6379
              password:
              database: 0
          代碼實(shí)現(xiàn)
          1. 定義過濾器AuthFilter
          2. 獲取請(qǐng)求對(duì)象 從請(qǐng)求頭或參數(shù)或cookie中獲取token(支持多種方式傳token對(duì)于客戶端更加友好,比如部分web下載請(qǐng)求會(huì)新建一個(gè)頁面,在請(qǐng)求頭中傳token處理起來比較麻煩)
          3. 沒有token,返回401
          4. 有token,查詢r(jià)edis是否有效
          5. 無效則返回401,有效則完成驗(yàn)證放行
          6. 重置token過期時(shí)間、添加內(nèi)部請(qǐng)求頭信息方便業(yè)務(wù)系統(tǒng)權(quán)限處理
          @Component
          public class AuthFilter implements GlobalFilterOrdered {

              @Autowired
              private RedisTemplate<String, String> redisTemplate;

              private static final String TOKEN_HEADER_KEY = "auth_token";

              @Override
              public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                  // 1.獲取請(qǐng)求對(duì)象
                  ServerHttpRequest request = exchange.getRequest();
                  // 2.獲取token
                  String token = getToken(request);
                  ServerHttpResponse response = exchange.getResponse();
                  if (StringUtils.isBlank(token)) {
                      // 3.token為空 返回401
                      response.setStatusCode(HttpStatus.UNAUTHORIZED);
                      return response.setComplete();
                  }
                  // 4.驗(yàn)證token是否有效
                  String userId = getUserIdByToken(token);
                  if (StringUtils.isBlank(userId)) {
                      // 5.token無效 返回401
                      response.setStatusCode(HttpStatus.UNAUTHORIZED);
                      return response.setComplete();
                  }
                  // token有效,后續(xù)業(yè)務(wù)處理
                  // 從寫請(qǐng)求頭,方便業(yè)務(wù)系統(tǒng)從請(qǐng)求頭獲取用戶id進(jìn)行權(quán)限相關(guān)處理
                  ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
                  request = builder.header("user_id", userId).build();
                  // 延長(zhǎng)緩存過期時(shí)間-token緩存用戶如果一直在操作就會(huì)一直重置過期
                  // 這樣避免用戶操作過程中突然過期影響業(yè)務(wù)操作及體驗(yàn),只有用戶操作間隔時(shí)間大于緩存過期時(shí)間才會(huì)過期
                  resetTokenExpirationTime(token, userId);
                  // 完成驗(yàn)證
                  return chain.filter(exchange);
              }


              @Override
              public int getOrder() {
                  // 優(yōu)先級(jí) 越小越優(yōu)先
                  return 0;
              }

              /**
               * 從redis中獲取用戶id
               * 在登錄操作時(shí)候 登陸成功會(huì)生成一個(gè)token, redis得key為auth_token:token 值為用戶id
               *
               * @param token
               * @return
               */

              private String getUserIdByToken(String token) {
                  String redisKey = String.join(":""auth_token", token);
                  return redisTemplate.opsForValue().get(redisKey);
              }

              /**
               * 重置token過期時(shí)間
               *
               * @param token
               * @param userId
               */

              private void resetTokenExpirationTime(String token, String userId) {
                  String redisKey = String.join(":""auth_token", token);
                  redisTemplate.opsForValue().set(redisKey, userId, 2, TimeUnit.HOURS);
              }


              /**
               * 獲取token
               *
               * @param request
               * @return
               */

              private static String getToken(ServerHttpRequest request) {
                  HttpHeaders headers = request.getHeaders();
                  // 從請(qǐng)求頭獲取token
                  String token = headers.getFirst(TOKEN_HEADER_KEY);
                  if (StringUtils.isBlank(token)) {
                      // 請(qǐng)求頭無token則從url獲取token
                      token = request.getQueryParams().getFirst(TOKEN_HEADER_KEY);
                  }
                  if (StringUtils.isBlank(token)) {
                      // 請(qǐng)求頭和url都沒有token則從cookies獲取
                      HttpCookie cookie = request.getCookies().getFirst(TOKEN_HEADER_KEY);
                      if (cookie != null) {
                          token = cookie.getValue();
                      }
                  }
                  return token;
              }
          }

          總結(jié)

          Gateway通過配置項(xiàng)可以實(shí)現(xiàn)路由功能,整合Nacos及配置監(jiān)聽可以實(shí)現(xiàn)動(dòng)態(tài)路由,實(shí)現(xiàn)GlobalFilter, Ordered兩個(gè)接口可以快速實(shí)現(xiàn)一個(gè)過濾器,文中也詳細(xì)的介紹了登錄后的請(qǐng)求鑒權(quán)流程,如果有不清楚地方可以評(píng)論區(qū)見咯。

          感謝閱讀,希望對(duì)你有所幫助 :) 

          來源:juejin.cn/post/7004756545741258765

          精彩推薦:

          Nginx 面試 40 連問,快頂不住了~~

          徹底搞懂 SpringBoot jar 可執(zhí)行原理

          保存好這個(gè)腳本,一鍵自動(dòng)部署 Redis 任意版本

          從零開始搭建公司大型SaaS 平臺(tái)架構(gòu)技術(shù)棧,這套架構(gòu)絕了!

          看看人家SpringBoot的全局異常處理多么優(yōu)雅...

          IDEA 插件版的 Postman,接口調(diào)試簡(jiǎn)直太方便了!粉了。。。

          瀏覽 32
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲视频观看 | 色色色色av | 最新做爱网站 | 一级操逼| 在线国产视频福利 |