<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>

          快速搭建一個網關服務,動態(tài)路由、鑒權的流程,看完秒會(含流程圖)

          共 17138字,需瀏覽 35分鐘

           ·

          2022-07-26 07:05

          點擊關注公眾號,Java干貨及時送達??

          前言

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

          搭建服務

          框架

          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>
          路由配置

          網關作為請求統(tǒng)一入口,路由就相當于是每個業(yè)務系統(tǒng)的入口,通過路由規(guī)則則可以匹配到對應微服務的入口,將請求命中到對應的業(yè)務系統(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

          解讀配置
          • 現在有一個服務demo-server部署在本機,地址和端口為127.0.0.1:8081,所以路由配置uri為http://localhost:8081
          • 使用網關服務路由到此服務,predicates -Path=/demo-server/**,網關服務的端口為8080,啟動網關服務,訪問localhost:8080/demo-server,路由斷言就會將請求路由到demo-server
          • 直接訪問demo-server的接口localhost:8081/api/test,通過網關的訪問地址則為localhost:8080/demo-server/api/test,predicates配置將請求斷言到此路由,filters-StripPrefix=1代表將地址中/后的第一個截取,所以demo-server就截取掉了

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

          動態(tài)路由

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

          Nacos配置

          groupId: 使用網關服務名稱即可

          dataId: routes

          配置格式:json

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

          json格式配置項與yaml中對應,需要了解配置在json中的寫法

          比對一下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
          代碼實現

          Nacos實現動態(tài)路由的方式核心就是通過Nacos配置監(jiān)聽,配置發(fā)生改變后執(zhí)行網關相關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路由配置,動態(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兩個接口用來定義過濾器,我們自定義過濾器只需要實現這個兩個接口即可

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

          通常一個網關服務的過濾主要包含 鑒權(是否登錄、是否黑名單、是否免登錄接口...) 限流(ip限流等等)功能,我們今天簡單介紹鑒權過濾器的流程實現

          鑒權過濾器

          需要實現鑒權過濾器,我們先得了解登錄及鑒權流程,如下圖所示

          由圖可知,我們鑒權過濾核心就是驗證token是否有效,所以我們網關服務需要與業(yè)務系統(tǒng)在同一個redis庫,先給網關添加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
          代碼實現
          1. 定義過濾器AuthFilter
          2. 獲取請求對象 從請求頭或參數或cookie中獲取token(支持多種方式傳token對于客戶端更加友好,比如部分web下載請求會新建一個頁面,在請求頭中傳token處理起來比較麻煩)
          3. 沒有token,返回401
          4. 有token,查詢redis是否有效
          5. 無效則返回401,有效則完成驗證放行
          6. 重置token過期時間、添加內部請求頭信息方便業(yè)務系統(tǒng)權限處理
          @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.獲取請求對象
                  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.驗證token是否有效
                  String userId = getUserIdByToken(token);
                  if (StringUtils.isBlank(userId)) {
                      // 5.token無效 返回401
                      response.setStatusCode(HttpStatus.UNAUTHORIZED);
                      return response.setComplete();
                  }
                  // token有效,后續(xù)業(yè)務處理
                  // 從寫請求頭,方便業(yè)務系統(tǒng)從請求頭獲取用戶id進行權限相關處理
                  ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
                  request = builder.header("user_id", userId).build();
                  // 延長緩存過期時間-token緩存用戶如果一直在操作就會一直重置過期
                  // 這樣避免用戶操作過程中突然過期影響業(yè)務操作及體驗,只有用戶操作間隔時間大于緩存過期時間才會過期
                  resetTokenExpirationTime(token, userId);
                  // 完成驗證
                  return chain.filter(exchange);
              }


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

              /**
               * 從redis中獲取用戶id
               * 在登錄操作時候 登陸成功會生成一個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過期時間
               *
               * @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();
                  // 從請求頭獲取token
                  String token = headers.getFirst(TOKEN_HEADER_KEY);
                  if (StringUtils.isBlank(token)) {
                      // 請求頭無token則從url獲取token
                      token = request.getQueryParams().getFirst(TOKEN_HEADER_KEY);
                  }
                  if (StringUtils.isBlank(token)) {
                      // 請求頭和url都沒有token則從cookies獲取
                      HttpCookie cookie = request.getCookies().getFirst(TOKEN_HEADER_KEY);
                      if (cookie != null) {
                          token = cookie.getValue();
                      }
                  }
                  return token;
              }
          }

          總結

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

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

          來源:juejin.cn/post/7004756545741258765

          1. 實現一個小輪子:用AOP實現異步上傳

          2. 面試難題:分布式 Session 實現難點,這篇就夠!

          3. Docker 火了!外部網絡可直接訪問映射到 127.0.0.1 的服務。。。

          4. Spring Event,賊好用的業(yè)務解耦神器!

          最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數據庫、數據結構等等。

          獲取方式:點“在看”,關注公眾號并回復 Java 領取,更多內容陸續(xù)奉上。

          PS:因公眾號平臺更改了推送規(guī)則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推送才會第一時間出現在你的訂閱列表里。

          “在看”支持小哈呀,謝謝啦??

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲三级在线 | 亚洲 A V中文字幕 | 永井玛利亚 精品 国产 一区 | 豆花成人理论在线电影一区二区 | 天天射日|