一切的業務開發都是基於需求的,首先看看需求:html
對訪問網關的請求進行token校驗,只有當token校驗經過時,才轉發到後端服務,不然直接返回401
本文給出的示例代碼適用場景:java
token存放在redis中, key爲用戶的uid
依賴的pom.xmlreact
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.winture</groupId> <artifactId>api-gateway</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>api-gateway</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
在Spring Cloud Gateway中,主要有兩種類型的過濾器:GlobalFilter 和 GatewayFiltergit
自定義GatewayFilter又有兩種實現方式,一種是直接 實現GatewayFilter接口,另外一種是 繼承AbstractGatewayFilterFactory類 ,任意選一種便可github
package com.winture.gateway.filter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.core.Ordered; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * token校驗過濾器 * @Version V1.0 */ public class AuthorizeGatewayFilter implements GatewayFilter, Ordered { private static final String AUTHORIZE_TOKEN = "token"; private static final String AUTHORIZE_UID = "uid"; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); String token = headers.getFirst(AUTHORIZE_TOKEN); String uid = headers.getFirst(AUTHORIZE_UID); if (token == null) { token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN); } if (uid == null) { uid = request.getQueryParams().getFirst(AUTHORIZE_UID); } ServerHttpResponse response = exchange.getResponse(); if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } String authToken = stringRedisTemplate.opsForValue().get(uid); if (authToken == null || !authToken.equals(token)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
首先從header頭信息中獲取uid和token信息,若是token或者uid爲null,則從請求參數中嘗試再次獲取,若是依然不存在token或者uid,則直接返回401狀態嗎,同時結束請求;若是二者都存在,則根據uid從redis中讀取保存的authToken,並和請求中傳輸的token進行比對,比對同樣則繼續經過過濾器鏈,不然直接結束請求,返回401.web
如何應用 AuthorizeGatewayFilter 呢?redis
package com.winture.gateway; import com.winture.gateway.filter.AuthorizeGatewayFilter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes().route(r -> r.path("/user/list") .uri("http://localhost:8077/api/user/list") .filters(new AuthorizeGatewayFilter()) .id("user-service")) .build(); } }
package com.winture.gateway.filter.factory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; @Component public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> { private static final Log logger = LogFactory.getLog(AuthorizeGatewayFilterFactory.class); private static final String AUTHORIZE_TOKEN = "token"; private static final String AUTHORIZE_UID = "uid"; @Autowired private StringRedisTemplate stringRedisTemplate; public AuthorizeGatewayFilterFactory() { super(Config.class); logger.info("Loaded GatewayFilterFactory [Authorize]"); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("enabled"); } @Override public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) { return (exchange, chain) -> { if (!config.isEnabled()) { return chain.filter(exchange); } ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); String token = headers.getFirst(AUTHORIZE_TOKEN); String uid = headers.getFirst(AUTHORIZE_UID); if (token == null) { token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN); } if (uid == null) { uid = request.getQueryParams().getFirst(AUTHORIZE_UID); } ServerHttpResponse response = exchange.getResponse(); if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } String authToken = stringRedisTemplate.opsForValue().get(uid); if (authToken == null || !authToken.equals(token)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } return chain.filter(exchange); }; } public static class Config { // 控制是否開啓認證 private boolean enabled; public Config() {} public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } } }
如何應用 AuthorizeGatewayFilterFactory 呢?spring
# 網關路由配置 spring: cloud: gateway: routes: - id: user-service uri: http://localhost:8077/api/user/list predicates: - Path=/user/list filters: # 關鍵在下面一句,值爲true則開啓認證,false則不開啓 # 這種配置方式和spring cloud gateway內置的GatewayFilterFactory一致 - Authorize=true
上面的兩種方式均可以實現對訪問網關的 特定請求 進行token校驗,若是想對 全部的請求 都進行token校驗,那麼能夠採用實現 GlobalFilter 方式。apache
package com.winture.gateway.filter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * token校驗全局過濾器 * @Version V1.0 */ @Component public class AuthorizeFilter implements GlobalFilter, Ordered { private static final String AUTHORIZE_TOKEN = "token"; private static final String AUTHORIZE_UID = "uid"; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); String token = headers.getFirst(AUTHORIZE_TOKEN); String uid = headers.getFirst(AUTHORIZE_UID); if (token == null) { token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN); } if (uid == null) { uid = request.getQueryParams().getFirst(AUTHORIZE_UID); } ServerHttpResponse response = exchange.getResponse(); if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } String authToken = stringRedisTemplate.opsForValue().get(uid); if (authToken == null || !authToken.equals(token)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
如何應用 AuthorizeFilter 呢?後端
只須要添加 @Component 註解,不須要進行任何額外的配置,實現GlobalFilter接口,自動會對全部的路由起做用
因爲剛接觸Spring Cloud Gateway,有些地方也不是特別熟悉,上面的示例代碼僅僅做爲參考,若是有錯誤的地方,還望指正。
備註:
參考學習: