网关层鉴权

接下来,就要在网关层增加与 Redis 相关的代码,实现鉴权功能,在网关层对请求进行前置的身份验证操作。

在网关层引入Redis

这个步骤与在用户微服务中引入 Redis 的步骤一致,主要是在网关模块引入 Redis 连接依赖、新增 Redis 连接配置项,之后在 newbee-mall-cloud-gateway-admin 模块下新建 ltd.gateway.cloud.newbee.config 包,并新增自定义的 Redis 配置类。

代码和配置项与前文类似,这里就不继续粘贴代码了。网关层的 Redis 连接配置需要与用户微服务中的 Redis 连接配置一致,这样才能够读取登录时的 token 值,读者可以根据本章提供的源代码学习和理解。

鉴权的全局过滤器编码实现

网关服务组件是客户端到微服务架构内部的一座桥梁,通过网关服务为请求提供了统一的访问入口,也能够对请求做一些定制化的前置处理。比如,在当前场景下,需要在网关层进行统一鉴权,这样就能够避免无正确身份id的请求直接进入微服务实例。如果请求头中有正确的身份 id,则放行,让后方的微服务实例进行请求处理;如果没有正确的身份 id,则直接在网关层响应一个错误提示即可。

在具体编码时,会涉及网关服务的过滤器知识点。本实战项目使用 Spring Cloud Gateway 的全局过滤器实现鉴权功能。

打开 newbee-mall-cloud-gateway-admin 工程,新建 ltd.gateway.cloud.newbee.filter 包,并新建全局过滤器 ValidTokenGlobalFilter,代码如下:

package ltd.gateway.cloud.newbee.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import ltd.common.cloud.newbee.dto.Result;
import ltd.common.cloud.newbee.dto.ResultGenerator;
import ltd.common.cloud.newbee.pojo.AdminUserToken;
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.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@Component
public class ValidTokenGlobalFilter implements GlobalFilter, Ordered {
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 登录接口,直接放行
        if (exchange.getRequest().getURI().getPath().equals("/users/admin/login")) {
            return chain.filter(exchange);
        }

        HttpHeaders headers = exchange.getRequest().getHeaders();

        if (headers == null || headers.isEmpty()) {
            // 返回错误提示
            return wrapErrorResponse(exchange, chain);
        }

        String token = headers.getFirst("token");

        if (!StringUtils.hasText(token)) {
            // 返回错误提示
            return wrapErrorResponse(exchange, chain);
        }

        ValueOperations<String, AdminUserToken> opsForAdminUserToken = redisTemplate.opsForValue();
        AdminUserToken tokenObject = opsForAdminUserToken.get(token);
        if (tokenObject == null) {
            // 返回错误提示
            return wrapErrorResponse(exchange, chain);
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    Mono<Void> wrapErrorResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
        Result result = ResultGenerator.genErrorResult(419, "无权限访问");
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode resultNode = mapper.valueToTree(result);
        byte[] bytes = resultNode.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer dataBuffer = exchange.getResponse().bufferFactory().wrap(bytes);
        exchange.getResponse().setStatusCode(HttpStatus.OK);
        return exchange.getResponse().writeWith(Flux.just(dataBuffer));
    }
}

主要的代码逻辑就在 filter() 方法中,处理步骤如下。

  1. 判断请求路径,如果是登录接口,则直接放行;如果不是登录接口,则进行后续处理。

  2. 获取请求头对象,如果为空,则直接返回错误提示,不会将请求转发到后方的微服务实例中;如果请求头不为空,则取出其中名称为 token 的值,如果该值不存在,则直接返回错误提示,不会将请求转发到后方的微服务实例中。

  3. 根据 token 值查询 Redis 数据库中是否存在对应的数据。如果 Redis 数据库中不存在对应的数据,则直接返回错误提示,不会将请求转发到后方的微服务实例中;如果 Redis 数据库中存在对应的数据,则表示鉴权成功,将请求转发到后方的微服务实例中去处理请求。

至此,网关层鉴权功能 所需的基本配置与功能代码已经完备,下一步进行功能测试。

功能测试

编码完成后,准备好数据库和表就可以启动项目了。当然,在项目启动前需要启动 Nacos ServerRedis Server,之后依次启动这两个项目。启动成功后,就可以进行用户登录功能与网关层鉴权功能的测试。

由于在网关层已经配置了路由转发,因此可以直接通过请求网关层的地址获得与请求后方微服务实例相同的效果。本来网关层只有请求转发的功能,经过代码整改和添加鉴权代码后,在网关层也能进行鉴权操作了。

这里,笔者使用 Postman 工具进行接口请求和功能测试,在地址栏中输入如下网址: http://localhost:29100/users/admin/profile

设置请求方法为 POST,测试效果如图 3-3 所示。

image 2025 04 23 12 54 39 171
Figure 1. 图3-3 不传 token 值时管理员用户信息请求测试效果

29100 是网关服务的端口号,这里通过直接访问网关层地址来获取管理员用户信息。因为没有在请求头中添加 token 字段,所以网关层的 token 过滤器直接拦截了这个请求,并返回错误提示。这个请求根本没有进入用户微服务实例,网关层鉴权生效了。

上述测试过程演示的是鉴权失败的情况,接下来通过请求登录接口获取正确的 token 值演示鉴权成功的情况。

首先获取一个 token 值,打开 Postman 工具,在地址栏中输入如下网址: http://localhost:29100/users/admin/login

设置请求方法为 POST,同时输入登录时所需要的用户名和密码,相关请求测试如图 3-4 所示。

这里通过直接访问网关层地址来请求用户微服务的登录接口,因为登录接口会被直接放行,所以由用户微服务来处理这个请求,最终得到了登录成功的结果及 token 值。

此时,可以查看 Redis 数据库中索引为 13 的数据,如图 3-5 所示。token 值被正确地存入 Redis 数据库,并且设置了过期时间。

image 2025 04 23 12 56 20 525
Figure 2. 图3-4 管理员用户登录请求测试
image 2025 04 23 12 56 35 317
Figure 3. 图3-5 Redis数据库中的数据

获取 token 值后再次打开 Postman 工具,请求管理员用户信息接口,在地址栏中输入如下网址: http://localhost:29100/users/admin/profile

设置请求方法为 POST,同时添加一个请求头参数,Key 为 “token”,Value 为上一个步骤获取的 token 值,管理员用户信息请求测试结果如图 3-6 所示。

image 2025 04 23 12 57 21 986
Figure 4. 图3-6 管理员用户信息请求测试结果

最终,成功获取了正确的数据,功能测试完成。当然,读者在测试时也可以选择 debug 模式启动两个项目,并打上几个断点,发起请求后看一下代码的执行步骤,会理解得更透彻一些。

本章涉及的业务编码并不复杂,主要是登录功能的改造和网关层鉴权功能的添加,涉及的知识点主要有对 Redis 数据库的操作、网关服务 Spring Cloud Gateway、全局过滤器的引入。在前面章节中学习的知识点和微服务组件开发经验正在一点一点地渗透到微服务架构项目中,这种实战过程的体验还是非常令人兴奋的,希望读者能够根据笔者提供的开发步骤顺利地完成本章的项目改造。