SpringCloud Gateway 身份認證

使用SpringCloud技術棧搭建微服務集羣,能夠選擇的組件比較多,因爲有些組件已經閉源或停更,這裏主要選用spring-cloud-alibaba做爲咱們的技術棧。java

  • 服務註冊與發現: nacos-discovery
  • 統一配置管理:nacos-config
  • 微服務網關:spring cloud gateway

因爲nacos自己就已是完備的服務,故參考官方文檔直接安裝使用就能夠,這裏重點介紹如何使用SpringCloud Gateway實現路由轉發和身份認證。nginx

1、微服務架構

微服務架構

  1. 全部的請求先經過nginx進行負載和轉發
  2. API Gateway負責進行微服務內的路由轉發和身份認證

2、實現路由轉發

1. 引入gateway包

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

須要注意的是:若是啓動時報錯,提示在依賴中發現的springMvc與gateway不能兼容,須要刪除spring-boot-starter-web相關引用git

**********************************************************

Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.

**********************************************************

2. 添加啓動類

@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
  • @EnableDiscoveryClient 用於集羣下的服務註冊與發現

3. 配置路由表

配置文件最好選用YAML,結構清晰易讀web

spring:
  application:
    name: cloud-api #服務名
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # nacos服務器地址
    gateway:
      routes:
        - id: cloud-user
          uri: lb://cloud-user  # 後端服務名
          predicates:
            - Path=/user/**   # 路由地址
          filters:
            - StripPrefix=1 # 去掉前綴

server:
  port: 8000

# 用於actuator暴露監控指標
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • StripPrefix=1 用於在路由轉發時去掉前綴地址,若無則將前綴一塊兒轉發給後端服務,好比:
請求地址爲: http://localhost:8000/user/home
在沒有加 StripPrefix時,轉發給後端服務地址爲: http://{cloud-user}/user/home,不然爲 http://{cloud-user}/home
  • management 配置用於暴露監控指標,可請求 http://localhost:8000/actuator/gateway/routes 獲取全部的映射路由

3、實現身份認證

在分佈式系統中有三種經常使用的身份認證方式:redis

1.使用Session,可以使用spring security來實現Session的管理 ,使用redis來存儲會話狀態,客戶端的sessionID須要cookie來存儲算法

Session時序圖

優勢spring

  • 使用方便,客戶端無感知
  • 安全性高
  • 會話管理支持較好

缺點後端

  • 對客戶端應用支持不友好
  • 沒法實現跨站跨端共享
  • 實現方式相對複雜
  • 須要客戶端Cookie支持

2.使用Token,由服務端簽發,並將用戶信息存儲在redis中,客戶端每次請求都帶上進行驗證api

token時序圖

優勢安全

  • 對多端共享支持友好
  • 對多端共享會話支持友好
  • 實現方式相對簡單
  • 安全性高
  • 無須Cookie支持

缺點

  • 會話過時時間維護較複雜
  • 服務端須要維持會話狀態

3.使用JWT,由服務端簽發且不保存會話狀態,客戶端每次請求都須要驗證合法性

jwt時序

優勢

  • 對多端共享支持友好
  • 對多端共享會話支持友好
  • 服務端無會話狀態
  • 無須Cookie支持
  • 可攜帶載荷數據

缺點

  • 會話過時時間維護較複雜
  • 默認狀況下,安全性較低
  • 一旦簽發沒法撤銷,或撤銷較複雜

簡單token驗證

本例子的token是uuid生成隨機碼的方式,沒有使用算法作驗證,這樣有可能致使客戶端窮舉token,不斷查詢redis形成風險。在生產環境中可以使用必定算法進行token簽發(如加密解密,有效時間戳等),保證僞造token對服務器的影響降到最低。

1. 用戶登錄保存session狀態

@Service
public class Session {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    Long expireTime = 10800L;

    /**
     * 保存session
     * @param loginUser
     */
    public void saveSession(LoginUser loginUser) {
        String key = String.format("login:user:%s", loginUser.userToken);

        redisTemplate.opsForValue().set(key, JSON.toJSONString(loginUser),
                expireTime, TimeUnit.SECONDS);
    }

    /**
     * 獲取session
     * @param token
     * @return
     */
    public LoginUser getSession(String token){
        String key = String.format("login:user:%s", token);

        String s = redisTemplate.opsForValue().get(key);
        if (Strings.isEmpty(s)){
            return null;
        }

        return JSON.parseObject(s, LoginUser.class);
    }
}

保存會話狀態時,須要設置過時時間,且不宜過長或太短。如進一步思考如何刷新會話過時時間。

2. 增長AuthCheckFilter,攔截路由請求

@Slf4j
@Component
public class AuthCheckFilter extends AbstractGatewayFilterFactory {

    @Autowired
    private Session session;

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {

            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            // 1. 獲取token
            String token = request.getHeaders().getFirst("token");

            log.info("當前請求的url:{}, method:{}", request.getURI().getPath(), request.getMethodValue());

            if (Strings.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }

            // 2. 驗證用戶是否已登錄
            LoginUser loginUser = this.session.getSession(token);
            if (loginUser == null) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }

            // 3. 將用戶名傳遞給後端服務
            ServerWebExchange build;
            try {
                ServerHttpRequest host = exchange.getRequest().mutate()
                        .header("X-User-Name", loginUser.userName)
                        // 中文字符須要編碼
                        .header("X-Real-Name", URLEncoder.encode(loginUser.realName, "utf-8"))
                        .build();
                build = exchange.mutate().request(host).build();
            } catch (UnsupportedEncodingException e) {
                build = exchange;
            }

            return chain.filter(build);
        };

    }
}

此攔截器做用爲驗證請求是否已登錄,不然返回401狀態,並將用戶會話信息傳遞給後端服務。

3. 配置Filter

在gateway項目的yml配置文件中配置須要進行驗證的路由filters: AuthCheckFilter

spring:
    gateway:
      routes:
        - id: cloud-user
          uri: lb://cloud-user  # 後端服務名
          predicates:
            - Path=/user/**   # 路由地址
          filters:
            - name: AuthCheckFilter     #會話驗證
            - StripPrefix=1 # 去掉前綴

由此就實現了對後端路由地址的身份驗證功能

3、完整代碼

https://gitee.com/hypier/barr...

請關注個人公衆號

請關注個人公衆號

相關文章
相關標籤/搜索