⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 segmentfault.com/a/1190000016227780 「十方」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

一切的业务开发都是基于需求的,首先看看需求:

对访问网关的请求进行token校验,只有当token校验通过时,才转发到后端服务,否则直接返回401

本文给出的示例代码适用场景:

token存放在redis中, key为用户的uid

依赖的pom.xml

<?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中,主要有两种类型的过滤器:GlobalFilterGatewayFilter

  • GlobalFilter : 全局过滤器,对所有的路由均起作用
  • GatewayFilter : 只对指定的路由起作用

1、自定义GatewayFilter

自定义GatewayFilter又有两种实现方式,一种是直接 实现GatewayFilter接口,另一种是 继承AbstractGatewayFilterFactory类 ,任意选一种即可

1.1 实现GatewayFilter接口

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.

如何应用 AuthorizeGatewayFilter 呢?

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();
}
}

1.2 继承AbstractGatewayFilterFactory类

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:
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 方式。

2、自定义GlobalFilter

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接口,自动会对所有的路由起作用

3、总结

由于刚接触Spring Cloud Gateway,有些地方也不是特别熟悉,上面的示例代码仅仅作为参考,如果有错误的地方,还望指正。

备注

  • 运行上面的代码,需要先启动redis服务,由于没有配置redis的地址和端口,默认采用localhost和6379端口,如果不一致,请自行在application.yml文件中配置即可;
  • 网关的端口采用默认的8080;
  • 当通过网关访问/user/list时,如果token验证通过,会转发到 http://localhost:8077/api/user/list 上,这是另外的一个接口服务,自行根据实际情况修改;

参考学习:

文章目录
  1. 1. 1、自定义GatewayFilter
    1. 1.1. 1.1 实现GatewayFilter接口
    2. 1.2. 1.2 继承AbstractGatewayFilterFactory类
  2. 2. 2、自定义GlobalFilter
  3. 3. 3、总结