业务流程

在上一节中我们已经引入了权限认证组件,我们希望如下图一样达到一个针对请求的鉴权功能

所以,本节就是要利用上一节做的权限认证功能进行实现API网关的权限仍证handler.

现在我们来回顾下,在未引入权限认证功能之时,我们的http请求是直接被接收处理后,开启会话进行请求RPC服务,而现在我们要进行权限认证,那么就不能再直接处理消息调用泛化调用,而是在原有基础上,进消息的接收和权限的验证后,再调用原有的消息处理和请求RPC服务。

即将网络请求分为三段:消息接收、请求鉴权、消息处理。这里的请求鉴权由 Shrio + JWT 完成。

为了满足消息的处理和鉴权,本节需要再引入2个Netty消息处理的Handler,分别是AuthorizationHandler、ProtocolDataHandler

  • 【新增】AuthorizationHandler 做接口的鉴权

  • 【新增】ProtocolDataHandler 做协议数据的处理

  • 【修改】GatewayServerHandler 原有网络协议处理类,作为第一个Netty通信管道中的处理类,只做简单参数解析,保存到管道中后即可放行。—— 后续再有一些需要优先处理的逻辑也会放到这个 GatewayServerHandler 类里完成。

除了上述本章最主要的功能模块外,还有一些其它的设计:

  • 【新增】GatewayResultMessage 做网关最后的返回结果封装

  • 【新增】AgreementConstants 保存协议解析的参数,供整个channal后续的handler可以看到,并且定义网关返回结果的枚举

  • 【修改】原执行器下的GatewayResult 变为 SessionResult(也就改了个名字),以让执行器返回的结果(实际上就是会话层最后返回的东西)与网关最后封装的结果做区分。

  • 【修改】HttpStatement 增添关于映射的请求是否需要鉴权的属性,这是因为某些网页可以不加鉴权的访问,例如淘宝的商品页面

本章节代码模块如下

流程实现

网关结果封装

首先我们先来定义最后的网关结果的返回(其实与以往做过项目的封装几乎一致)

 package com.zshunbao.gateway.socket.agreement;
 ​
 /**
  * @program: api-gateway-core
  * @ClassName GatewayResultMessage
  * @description: 网关结果封装
  * @author: zs宝
  * @create: 2025-08-15 15:26
  * @Version 1.0
  **/
 public class GatewayResultMessage {
     private String code;
     private String info;
     private Object data;
 ​
     protected GatewayResultMessage(String code, String info, Object data) {
         this.code = code;
         this.info = info;
         this.data = data;
     }
     public static GatewayResultMessage buildSuccess(Object data) {
         return new GatewayResultMessage(AgreementConstants.ResponseCode._200.getCode(), AgreementConstants.ResponseCode._200.getInfo(), data );
     }
 ​
     public static GatewayResultMessage buildError(String code, String info) {
         return new GatewayResultMessage(code, info, null);
     }
 ​
     public String getCode() {
         return code;
     }
 ​
     public String getInfo() {
         return info;
     }
 ​
     public Object getData() {
         return data;
     }
 }
 ​

然后是关于封装结果的code,info的枚举类

 package com.zshunbao.gateway.socket.agreement;
 ​
 import io.netty.util.AttributeKey;
 import com.zshunbao.gateway.mapping.HttpStatement;
 /**
  * @program: api-gateway-core
  * @ClassName AgreementConstants
  * @description: 自定义网关返回结果的业务参数
  * @author: zs宝
  * @create: 2025-08-15 15:26
  * @Version 1.0
  **/
 public class AgreementConstants {
     public static final AttributeKey<HttpStatement> HTTP_STATEMENT = AttributeKey.valueOf("HttpStatement");
     public enum ResponseCode {
         // 访问成功
         _200("200","访问成功"),
         // 后端接收数据的数据类型不匹配 ,比如前端传送的数据时string,后端使用的是Integer数据类型接收,此时就会包以上错误
         _400("400","接收数据的数据类型不匹配"),
         // (禁止) 服务器拒绝请求。资源不可用,服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致,比如IIS或者apache设置了访问权限不当。
         _403("403","服务器拒绝请求"),
         // (未找到) 服务器找不到请求的网页;输入链接有误。第一个4表示客户端出错,第二个 0 表示你把网址打错了,最后的那个4表示 “Not Found”,即找不到网页。
         _404("404","服务器找不到请求的网页,输入链接有误"),
         // (服务器内部错误) 服务器遇到错误,无法完成请求。
         _500("500","服务器遇到错误,无法完成请求"),
         // (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。如 RPC 提供方宕机。
         _502("502","服务器作为网关或代理,从上游服务器收到无效响应");
         private String code;
         private String info;
 ​
         ResponseCode(String code, String info) {
             this.code = code;
             this.info = info;
         }
 ​
         public String getCode() {
             return code;
         }
 ​
         public String getInfo() {
             return info;
         }
     }
 }
 ​

注意这其中有个HTTP_STATEMENT其中定义着当前channel可以共享的有关的映射信息。

鉴权

现在我们来看下主要的鉴权功能的handler

首先是消息进入,我们要对其做接收处理

 package com.zshunbao.gateway.socket.handlers;
 ​
 import com.zshunbao.gateway.mapping.HttpStatement;
 import com.zshunbao.gateway.session.Configuration;
 import com.zshunbao.gateway.socket.BaseHandler;
 import com.zshunbao.gateway.socket.agreement.AgreementConstants;
 import com.zshunbao.gateway.socket.agreement.GatewayResultMessage;
 import com.zshunbao.gateway.socket.agreement.RequestParser;
 import com.zshunbao.gateway.socket.agreement.ResponseParser;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.DefaultFullHttpResponse;
 import io.netty.handler.codec.http.FullHttpRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 ​
 /**
  * @program: api-gateway-core
  * @ClassName GatewayServerHandler
  * @description: 网络协议处理器
  * @author: zs宝
  * @create: 2025-08-15 15:37
  * @Version 1.0
  **/
 public class GatewayServerHandler extends BaseHandler<FullHttpRequest> {
     private final Logger logger = LoggerFactory.getLogger(GatewayServerHandler.class);
     private final Configuration configuration;
 ​
     public GatewayServerHandler(Configuration configuration) {
         this.configuration = configuration;
     }
 ​
     @Override
     protected void session(ChannelHandlerContext ctx, Channel channel, FullHttpRequest request) {
         logger.info("网关接收请求【全局】 uri:{} method:{}", request.uri(), request.method());
         try {
             //1. 解析参数
             RequestParser requestParser = new RequestParser(request);
             String uri = requestParser.getUri();
             //2、保存信息;HttpStatement、Header=token
             HttpStatement httpStatement = configuration.getHttpStatement(uri);
             channel.attr(AgreementConstants.HTTP_STATEMENT).set(httpStatement);
 ​
             //3、放行
             request.retain();
             ctx.fireChannelRead(request);
         } catch (Exception e) {
             // 4. 封装返回结果
             DefaultFullHttpResponse response = new ResponseParser().parse(GatewayResultMessage.buildError(AgreementConstants.ResponseCode._500.getCode(), "网关协议调用失败!" + e.getMessage()));
             channel.writeAndFlush(response);
         }
 ​
     }
 }
 ​

在这个地方,我们将映射信息进行保存,方便后续handler可以直接使用。

  • GatewayServerHandler 处理类由原来的处理数据,修改解析参数,获取 HttpStatement 操作。

  • 因为获取 HttpStatement 后可以保存到管道的属性信息中,所有的这条通信链上都可以获取到,这样到鉴权处理中直接获取到信息就可以操作了。

  • 这里不会获取到会话的信息(gatewaySessionFactory.openSession(uri)),避免如果鉴权都鉴权失败了,创建会话服务也是浪费资源。所以只需要在构造函数中传输 Configuration 即可,用于根据 URI 获取 HttpStatement 网关接口映射信息,方便拿到是否需要鉴权。

然后当消息接收后,就要进行鉴权处理

 package com.zshunbao.gateway.socket.handlers;
 ​
 import com.zshunbao.gateway.mapping.HttpStatement;
 import com.zshunbao.gateway.session.Configuration;
 import com.zshunbao.gateway.socket.BaseHandler;
 import com.zshunbao.gateway.socket.agreement.AgreementConstants;
 import com.zshunbao.gateway.socket.agreement.GatewayResultMessage;
 import com.zshunbao.gateway.socket.agreement.ResponseParser;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.DefaultFullHttpResponse;
 import io.netty.handler.codec.http.FullHttpRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 /**
  * @program: api-gateway-core
  * @ClassName AuthorizationHandler
  * @description: 鉴权hanlder
  * @author: zs宝
  * @create: 2025-08-15 15:49
  * @Version 1.0
  **/
 public class AuthorizationHandler extends BaseHandler<FullHttpRequest> {
     private final Logger logger = LoggerFactory.getLogger(AuthorizationHandler.class);
 ​
     private final Configuration configuration;
 ​
 ​
     public AuthorizationHandler(Configuration configuration) {
         this.configuration = configuration;
     }
 ​
     @Override
     protected void session(ChannelHandlerContext ctx, Channel channel, FullHttpRequest request) {
         logger.info("网关接收请求【鉴权】 uri:{} method:{}", request.uri(), request.method());
         try {
             HttpStatement httpStatement = channel.attr(AgreementConstants.HTTP_STATEMENT).get();
             if(httpStatement.isAuth()){
                 try {
                     //先拿到鉴权所需要的配置
                     String token = request.headers().get("token");
                     String uid = request.headers().get("uid");
                     //进行鉴权:shiro + jwt
                     boolean validate = configuration.validate(uid, token);
                     if(validate){
                         //鉴权通过,放行
                         request.retain();
                         ctx.fireChannelRead(request);
                     }else {
                         //鉴权为通过,直接返回结果
                         DefaultFullHttpResponse response = new ResponseParser().parse(GatewayResultMessage.buildError(AgreementConstants.ResponseCode._403.getCode(), "对不起,你无权访问此接口!"));
                         channel.writeAndFlush(response);
                     }
                 }catch (Exception e){
                     DefaultFullHttpResponse response = new ResponseParser().parse(GatewayResultMessage.buildError(AgreementConstants.ResponseCode._403.getCode(), "对不起,你的鉴权不合法!"));
                     channel.writeAndFlush(response);
                 }
             }else {
                 //不需要鉴权,直接放行
                 request.retain();
                 ctx.fireChannelRead(request);
             }
         }catch (Exception e){
             // 4. 封装返回结果
             DefaultFullHttpResponse response = new ResponseParser().parse(GatewayResultMessage.buildError(AgreementConstants.ResponseCode._500.getCode(), "网关协议调用失败!" + e.getMessage()));
             channel.writeAndFlush(response);
         }
     }
 }
 ​

这里其实就是根据映射信息HttpStatement中的属性字段auth来判断当前访问的资源是否需要鉴权,不需要则直接放行,需要则进行鉴权。

不过这里的鉴权操作不是直接调用的上一节我们定义的组件,而是在configuration中定义,我们将对应的鉴权配置到了Configuration 配置项中,做统一的管理。

最后如果鉴权handler正常通过,那么接下来就和之前的操作一样,先解析参数,然后调用RPC服务,封装结果

 /**
  * @program: api-gateway-core
  * @ClassName ProtocolDataHandler
  * @description:
  * @author: zs宝
  * @create: 2025-08-04 15:33
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.socket.handlers;
 ​
 ​
 import com.zshunbao.gateway.bind.IGenericReference;
 import com.zshunbao.gateway.executor.result.SessionResult;
 import com.zshunbao.gateway.session.GatewaySession;
 import com.zshunbao.gateway.session.defaults.DefaultGatewaySessionFactory;
 import com.zshunbao.gateway.socket.BaseHandler;
 import com.zshunbao.gateway.socket.agreement.AgreementConstants;
 import com.zshunbao.gateway.socket.agreement.GatewayResultMessage;
 import com.zshunbao.gateway.socket.agreement.RequestParser;
 import com.zshunbao.gateway.socket.agreement.ResponseParser;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 import java.util.Map;
 ​
 public class ProtocolDataHandler extends BaseHandler<FullHttpRequest> {
     private final Logger logger = LoggerFactory.getLogger(ProtocolDataHandler.class);
 ​
     private final DefaultGatewaySessionFactory gatewaySessionFactory;
 ​
     public ProtocolDataHandler(DefaultGatewaySessionFactory gatewaySessionFactory) {
         this.gatewaySessionFactory = gatewaySessionFactory;
     }
 ​
     @Override
     protected void session(ChannelHandlerContext ctx, Channel channel, FullHttpRequest request) {
         logger.info("网关接收请求 uri:{} method:{}", request.uri(), request.method());
         try {
             //1、解析请求参数
             RequestParser requestParser = new RequestParser(request);
             String uri= requestParser.getUri();
             if (uri==null || uri.equals("/favicon.ico")) return;
             Map<String, Object> args = requestParser.parse();
             //2、创建会话
             GatewaySession gatewaySession = gatewaySessionFactory.openSession(uri);
             //根据uri获得对应的mapper映射,即本地动态创建的代理类
             IGenericReference reference = gatewaySession.getMapper();
             //执行代理类的方法,这个方法会被拦截,然后执行Dubbo引用缓存到本地真正的泛化代理对象,从而调用远程方法执行,得到结果
             SessionResult result = reference.$invoke(args);
 ​
             //3、封装返回结果:注意由sessionResult到网关定义的返回结果的转化
             DefaultFullHttpResponse response = new ResponseParser().parse("0000".equals(result.getCode()) ? GatewayResultMessage.buildSuccess(result.getData()) : GatewayResultMessage.buildError(AgreementConstants.ResponseCode._404.getCode(), "网关协议调用失败!"));
 ​
             channel.writeAndFlush(response);
         } catch (Exception e) {
             //4封装错误的结果
             DefaultFullHttpResponse response=new ResponseParser().parse(GatewayResultMessage.buildError(AgreementConstants.ResponseCode._502.getCode(),"网关调用失败"+e.getMessage()));
             channel.writeAndFlush(response);
         }
     }
 }
 ​

注意这里最后的返回结果做了封装(这是唯一与之前不同的地方)

到此其实本节的主要内容已经写完了

修改部分

这一部分主要展示一些修改了的部分代码,都是小修改

Configuration增减鉴权封装

 /**
  * @program: api-gateway-core
  * @ClassName Configuration
  * @description:
  * @author: zs宝
  * @create: 2025-08-04 14:56
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session;
 ​
 import com.zshunbao.gateway.authorization.IAuth;
 import com.zshunbao.gateway.authorization.auth.AuthService;
 import com.zshunbao.gateway.bind.IGenericReference;
 import com.zshunbao.gateway.bind.MapperRegistry;
 import com.zshunbao.gateway.datasource.Connection;
 import com.zshunbao.gateway.executor.Executor;
 import com.zshunbao.gateway.executor.SimpleExecutor;
 import com.zshunbao.gateway.mapping.HttpStatement;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.config.ReferenceConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.rpc.service.GenericService;
 ​
 import java.util.HashMap;
 import java.util.Map;
 ​
 public class Configuration {
     //对应的注册器
     private final MapperRegistry mapperRegistry=new MapperRegistry(this);
     private final Map<String, HttpStatement> httpStatements = new HashMap<>();
 ​
     //根据官方文档的示例,dubbo的泛化调用的API调用方式主要有3个东西需要配置
     //分别是ApplicationConfig,RegistryConfig,ReferenceConfig<GenericService>
     //但是我们这是一个网关,被调用的服务方可能不止一个,因此,我们需要按照对应的服务名和配置名,将其对应起来保存
     //RPC 应用服务配置项
     //注意键为应用名称;值为应用服务配置项
     private final Map<String, ApplicationConfig> applicationConfigMap=new HashMap<>();
     //RPC 注册中心配置项
     //键为应用名称;值为注册中心配置项
     private final Map<String, RegistryConfig> registryConfigMap=new HashMap<>();
     //RPC 泛化服务配置项
     //注意这里的键:远程服务接口的全限定类名 ; 值为泛化引用配置对象
     private final Map<String, ReferenceConfig<GenericService>> referenceConfigMap=new HashMap<>();
     //配置鉴权
     private IAuth auth=new AuthService();
 ​
     public Configuration(){
         //TODO 后期从配置中获取,本节主要内容是泛化服务的调用
         ApplicationConfig applicationConfig=new ApplicationConfig();
         applicationConfig.setName("api-gateway-test");
         applicationConfig.setQosEnable(false);
 ​
         RegistryConfig registryConfig=new RegistryConfig();
         //配置应用在那个注册中心可以被调用
         registryConfig.setAddress("zookeeper://127.0.0.1:2181");
         registryConfig.setRegister(false);
 ​
         //对应的泛化服务配置
         ReferenceConfig<GenericService> reference=new ReferenceConfig<>();
         //配置泛化调用的服务方接口
         reference.setInterface("cn.bugstack.gateway.rpc.IActivityBooth");
         reference.setVersion("1.0.0");
         reference.setGeneric("true");
 ​
         //加入缓存中去
         applicationConfigMap.put("api-gateway-test",applicationConfig);
         registryConfigMap.put("api-gateway-test",registryConfig);
         referenceConfigMap.put("cn.bugstack.gateway.rpc.IActivityBooth",reference);
 ​
     }
     //对应的服务配置项对外get方法
     public ApplicationConfig getApplicationConfig(String applicationName) {
         return applicationConfigMap.get(applicationName);
     }
 ​
     //对应的对外注册中心的get方法
     public RegistryConfig getRegistryConfig(String applicationName) {
         return registryConfigMap.get(applicationName);
     }
 ​
     //对应的对外泛化调用配置项get方法
     public ReferenceConfig<GenericService> getReferenceConfig(String interfaceName) {
         return referenceConfigMap.get(interfaceName);
     }
 ​
     //添加泛化调用方法
     public void addMapper(HttpStatement httpStatement) {
         mapperRegistry.addMapper(httpStatement);
     }
 ​
     public IGenericReference getMapper(String uri, GatewaySession gatewaySession) {
         return mapperRegistry.getMapper(uri, gatewaySession);
     }
 ​
     public void addHttpStatement(HttpStatement httpStatement) {
         httpStatements.put(httpStatement.getUri(), httpStatement);
     }
 ​
     public HttpStatement getHttpStatement(String uri) {
         return httpStatements.get(uri);
     }
 ​
     public Executor newExecutor(Connection connection) {
         return new SimpleExecutor(this,connection);
     }
 ​
     public boolean validate(String uid, String token) {
         return auth.validate(uid,token);
     }
 ​
 }
 ​

HttpStatement映射信息增加是否鉴权字段

 /**
  * @program: api-gateway-core
  * @ClassName HttpStatement
  * @description: 网关接口映射信息
  * @author: zs宝
  * @create: 2025-08-04 14:40
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.mapping;
 ​
 public class HttpStatement {
     /** 应用名称; */
     private String application;
     /** 服务接口;RPC、其他 */
     private String interfaceName;
     /** 服务方法;RPC#method */
     private String methodName;
     /** 参数类型(RPC 限定单参数注册);new String[]{"java.lang.String"}、new String[]{"cn.bugstack.gateway.rpc.dto.XReq"} */
     private String parameterType;
     /** 网关接口 */
     private String uri;
     /** 接口类型;GET、POST、PUT、DELETE */
     private HttpCommandType httpCommandType;
     /** 是否需要鉴权处理  true=是 false=否 */
     private boolean auth;
 ​
     public HttpStatement(String application, String interfaceName, String methodName, String parameterType, String uri, HttpCommandType httpCommandType, boolean auth){
         this.application = application;
         this.interfaceName = interfaceName;
         this.methodName = methodName;
         this.parameterType = parameterType;
         this.uri = uri;
         this.httpCommandType = httpCommandType;
         this.auth = auth;
     }
 ​
     public String getApplication() {
         return application;
     }
 ​
     public String getInterfaceName() {
         return interfaceName;
     }
 ​
     public String getMethodName() {
         return methodName;
     }
 ​
     public String getUri() {
         return uri;
     }
 ​
     public HttpCommandType getHttpCommandType() {
         return httpCommandType;
     }
 ​
     public String getParameterType() {
         return parameterType;
     }
 ​
     public boolean isAuth() {
         return auth;
     }
 }
 ​

SessionChannelInitializer增加新定义的handler,注意GatewayServerHandler和AuthorizationHandler都只加载了Configuration配置类,没有加载会话工厂gatewaySessionFactory,因为鉴权可能不通过,就没有必要浪费资源加载会话,因为鉴权不通过根本不会有开启会话的机会

 /**
  * @program: api-gateway-core
  * @ClassName SessionChannelInitializer
  * @description:自定义netty服务端链接的childHandler的初始化工具
  * @author: zs宝
  * @create: 2025-07-25 16:56
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.socket;
 ​
 ​
 ​
 import com.zshunbao.gateway.session.Configuration;
 import com.zshunbao.gateway.session.defaults.DefaultGatewaySessionFactory;
 ​
 import com.zshunbao.gateway.socket.handlers.AuthorizationHandler;
 import com.zshunbao.gateway.socket.handlers.GatewayServerHandler;
 import com.zshunbao.gateway.socket.handlers.ProtocolDataHandler;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelPipeline;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.handler.codec.http.HttpObjectAggregator;
 import io.netty.handler.codec.http.HttpRequestDecoder;
 import io.netty.handler.codec.http.HttpResponseEncoder;
 ​
 public class SessionChannelInitializer extends ChannelInitializer<SocketChannel>{
 ​
     private DefaultGatewaySessionFactory gatewaySessionFactory;
     private final Configuration configuration;
 ​
     public SessionChannelInitializer(DefaultGatewaySessionFactory gatewaySessionFactory, Configuration configuration) {
         this.gatewaySessionFactory = gatewaySessionFactory;
         this.configuration = configuration;
     }
 ​
     @Override
     protected void initChannel(SocketChannel channel) throws Exception {
         //得到处理的流水线
         ChannelPipeline pipeline = channel.pipeline();
         pipeline.addLast(new HttpRequestDecoder());
         pipeline.addLast(new HttpResponseEncoder());
         pipeline.addLast(new HttpObjectAggregator(1024*1024));
         pipeline.addLast(new GatewayServerHandler(configuration));
         pipeline.addLast(new AuthorizationHandler(configuration));
         pipeline.addLast(new ProtocolDataHandler(gatewaySessionFactory));
     }
 }
 ​

其次之前的鉴权组件,做了一点点小修改

 package com.zshunbao.gateway.authorization;
 ​
 import io.jsonwebtoken.Claims;
 import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.AuthenticationInfo;
 import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.authc.SimpleAuthenticationInfo;
 import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.realm.AuthorizingRealm;
 import org.apache.shiro.subject.PrincipalCollection;
 ​
 /**
  * @program: api-gateway-core
  * @ClassName GatewayAuthorizingRealm
  * @description: 验证领域
  * @author: zs宝
  * @create: 2025-08-12 15:59
  * @Version 1.0
  **/
 public class GatewayAuthorizingRealm extends AuthorizingRealm {
 ​
     @Override
     public Class<?> getAuthenticationTokenClass(){
         return GatewayAuthorizingToken.class;
     }
 ​
     @Override
     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
         return null;
     }
 ​
     @Override
     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
         try {
             //验证解析是否报错
             Claims claims = JwtUtil.decode(((GatewayAuthorizingToken) token).getJwt());
             //验证签发人是否匹配
             if(!token.getPrincipal().equals(claims.getSubject())){
                 throw new AuthenticationException("无效令牌");
             }
         }catch (Exception e){
             throw new AuthenticationException("无效令牌");
         }
         return new SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),this.getName());
     }
 }
 ​

最后,对外统一的的有关动态代理方法执行的接口返回结果做了小修改

 /**
  * @program: api-gateway-core
  * @ClassName IGenericReference
  * @description: 统一泛化调用接口,无论怎样的HTTP请求,我们暴露出去的都是这个接口,一致
  * @author: zs宝
  * @create: 2025-07-29 15:53
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.bind;
 ​
 import com.zshunbao.gateway.executor.result.SessionResult;
 ​
 import java.util.Map;
 ​
 public interface IGenericReference {
     SessionResult $invoke(Map<String, Object>params);
 }
 ​

测试

测试代码

 /**
 * @program: api-gateway-core
 * @ClassName ApiTest
 * @description: 
 * @author: zs宝
 * @create: 2025-08-09 17:21
 * @Version 1.0
 **/
 ​
 package com.zshunbao.gateway.test;
 ​
 import com.zshunbao.gateway.mapping.HttpCommandType;
 import com.zshunbao.gateway.mapping.HttpStatement;
 import com.zshunbao.gateway.session.Configuration;
 import com.zshunbao.gateway.session.defaults.DefaultGatewaySessionFactory;
 import com.zshunbao.gateway.socket.GatewaySocketServer;
 import io.netty.channel.Channel;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 ​
 public class ApiTest {
     private final Logger logger = LoggerFactory.getLogger(ApiTest.class);
 ​
     /**
      * 测试:
      * http://localhost:7397/wg/activity/sayHi
      * 参数:
      * {
      *     "str": "10001"
      * }
      *
      * http://localhost:7397/wg/activity/insert
      * 参数:
      * {
      *     "name":"zshunbao",
      *     "uid":"10001"
      * }
      */
     @Test
     public void test_gateway() throws InterruptedException, ExecutionException {
         // 1. 创建配置信息加载注册
         Configuration configuration = new Configuration();
 ​
         HttpStatement httpStatement01 = new HttpStatement(
                 "api-gateway-test",
                 "cn.bugstack.gateway.rpc.IActivityBooth",
                 "sayHi",
                 "java.lang.String",
                 "/wg/activity/sayHi",
                 HttpCommandType.GET,
                 false
         );
 ​
         HttpStatement httpStatement02 = new HttpStatement(
                 "api-gateway-test",
                 "cn.bugstack.gateway.rpc.IActivityBooth",
                 "insert",
                 "cn.bugstack.gateway.rpc.dto.XReq",
                 "/wg/activity/insert",
                 HttpCommandType.POST,
                 true
         );
 ​
         configuration.addMapper(httpStatement01);
         configuration.addMapper(httpStatement02);
 ​
         // 2. 基于配置构建会话工厂
         DefaultGatewaySessionFactory gatewaySessionFactory = new DefaultGatewaySessionFactory(configuration);
 ​
         // 3. 创建启动网关网络服务
         GatewaySocketServer server = new GatewaySocketServer(gatewaySessionFactory,configuration);
 ​
         Future<Channel> future = Executors.newFixedThreadPool(2).submit(server);
         Channel channel = future.get();
 ​
         if (null == channel) throw new RuntimeException("netty server start error channel is null");
 ​
         while (!channel.isActive()) {
             logger.info("netty server gateway start Ing ...");
             Thread.sleep(500);
         }
         logger.info("netty server gateway start Done! {}", channel.localAddress());
 ​
         Thread.sleep(Long.MAX_VALUE);
     }
 }
 ​

注意由于本次测试主要时测试鉴权功能

因此在需要有自己的token

 package com.zshunbao.gateway.test;
 ​
 ​
 import com.zshunbao.gateway.authorization.IAuth;
 import com.zshunbao.gateway.authorization.JwtUtil;
 import com.zshunbao.gateway.authorization.auth.AuthService;
 import io.jsonwebtoken.Claims;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.config.IniSecurityManagerFactory;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.Factory;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 import java.util.HashMap;
 import java.util.Map;
 ​
 ​
 public class ShiroTest {
 ​
     private final static Logger logger = LoggerFactory.getLogger(ShiroTest.class);
 ​
     @Test
     public void test_auth_service() {
         IAuth auth = new AuthService();
         boolean validate = auth.validate("zshunbao", "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4aWFvZnVnZSIsImV4cCI6MTY2NjQwNDAxMiwiaWF0IjoxNjY1Nzk5MjEyLCJrZXkiOiJ4aWFvZnVnZSJ9.Vs-ObO5OF2pYr7jkt0N4goq0hErOZNdyqfacHzbkfHM");
         System.out.println(validate ? "验证成功" : "验证失败");
     }
 ​
     @Test
     public void test_awt() {
         String issuer = "zshunbao";
         long ttlMillis = 7 * 24 * 60 * 60 * 1000L;
         Map<String, Object> claims = new HashMap<>();
         claims.put("key", "zshunbao");
 ​
         // 编码
         String token = JwtUtil.encode(issuer, ttlMillis, claims);
         System.out.println(token);
 ​
         // 解码
         Claims parser = JwtUtil.decode(token);
         System.out.println(parser.getSubject());
     }
 ​
 }
 ​

注意下面使用api_post测试时token由于时间过期的原因最好在测试时执行test_awt()方法生成一个新的

token正确

token错误

不需要token的请求

参考资料