一切的业务开发都是基于需求的,首先看看需求: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,有些地方也不是特别熟悉,上面的示例代码仅仅做为参考,若是有错误的地方,还望指正。
备注:
参考学习: