第八章 网关会话鉴权处理

通过拆分 Handler 通信管道处理器,引入 Shiro + JWT 到 AuthorizationHandler 中处理鉴权操作。

设计

一次网络请求经过 Netty 处理可以分为三段;消息接收、请求鉴权、消息处理。这样就由原来我们只是在接收消息后直接把消息协议转换后请求到 RPC 服务,转换为三层 Handler 来处理简单的消息接收、请求鉴权以及 RPC 泛化调用处理。

8-设计

实现

Authorization 部分:

  • 提供出进行 Shiro + JWT 进行鉴权的接口。

Mapping 部分:

  • 在 HttpStatement 中新增布尔类型的 auth 属性,用于标记每个HTTP请求是否需要鉴权。例如GET请求并不一定需要鉴权,可以以游客的身份访问;用户账户信息则需要鉴权后才能访问。
  • 将 IGenericReference 接口返回类型由 Object 具体为 SessionResult,方便后续直接使用包装的结果,而不用强转类型。

DataSource部分:

  • 无变动

Session部分:

  • 在 Configuration 中新增 AuthService 鉴权服务以及使用该服务的鉴权方法,便于后续在 Handler 中鉴权,另外新增注册 RPC 配置的方法,注入对应的 RPC 配置,便于后续构建 Dubbo 服务,并获取泛化调用服务。

Socket部分:

  • GatewayServerHandler 前置解析

    • 这是请求鉴权的前置处理器,这里先简单处理,获取 HTTP 请求中的 URI ,用 URI 获取服务启动时注册的 HttpStatement 对象。
    • 将 HttpStatement 对象保存至 channel#attr,则这条通信链路上的其他处理器都可以获取并使用该对象。
    • ctx.fireChannelRead(request); // 将request请求传递给下一个 handler
    • 另外使用 request.retain(); // 增加request的引用计数,这是为了确保在后续处理中request不会被意外释放
    • 而原来在该处理器中的一些会话操作,都后移了,这是为了先鉴权,等鉴权通过后再继续执行会话操作,避免提前开启会话造成资源浪费。
  • AuthorizationHandler 接口鉴权

    • 获取上一个处理器传递的 HttpStatement 对象中的 auth 属性,属性值为 false ,则无需鉴权,直接放行交给下一个处理器。如果需要鉴权,则调用 Confguration#AuthService 对 HTTP 请求进行鉴权,鉴权成功则放行。
  • ProtocolDataHandler 泛化调用处理

    • 该处理器就是之前章节的GatewayServerHandler,只是在本章更新为ProtocoiDatalandler,用于泛化调用服务。
  • 新增 AgreementConstants ,枚举 HTTP 返回状态码

  • 新增 GatewayResult ,用于封装网关的返回结果

配置定义

HttpStatement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package cn.ray.gateway.mapping;

/**
* @author Ray
* @date 2023/5/13 17:13
* @description 网关接口映射信息
*/
public class HttpStatement {

/**
* 应用名称
*/
private String application;

/**
* 服务接口
*/
private String interfaceName;

/**
* 服务方法
*/
private String methodName;

/**
* 统一资源标识符 queryUser
*/
private String uri;

/**
* 请求类型
*/
private HttpRequestType httpRequestType;

/** 参数类型(RPC 限定单参数注册);new String[]{"java.lang.String"}、new String[]{"cn.bugstack.gateway.rpc.dto.XReq"} */
private String parameterType;

/** 是否鉴权;true = 是、false = 否 */
private boolean auth;

public HttpStatement(String application, String interfaceName, String methodName, String uri, HttpRequestType httpRequestType, String parameterType, boolean auth) {
this.application = application;
this.interfaceName = interfaceName;
this.methodName = methodName;
this.uri = uri;
this.httpRequestType = httpRequestType;
this.parameterType = parameterType;
this.auth = auth;
}

public String getApplication() {
return application;
}

public String getInterfaceName() {
return interfaceName;
}

public String getMethodName() {
return methodName;
}

public String getUri() {
return uri;
}

public HttpRequestType getHttpRequestType() {
return httpRequestType;
}

public String getParameterType() {
return parameterType;
}

public boolean isAuth() {
return auth;
}
}

Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package cn.ray.gateway.session;

import cn.ray.gateway.authorization.IAuth;
import cn.ray.gateway.authorization.auth.AuthService;
import cn.ray.gateway.bind.MapperRegistry;
import cn.ray.gateway.bind.IGenericReference;
import cn.ray.gateway.datasource.Connection;
import cn.ray.gateway.executor.Executor;
import cn.ray.gateway.executor.SimpleExecutor;
import cn.ray.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;

/**
* @author Ray
* @date 2023/5/12 21:47
* @description 会话生命周期配置项
*/
public class Configuration {

private final MapperRegistry mapperRegistry = new MapperRegistry(this);

private final IAuth auth = new AuthService();

private final Map<String, HttpStatement> httpStatementMap = new HashMap<>();

/**
* 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<>();

public Configuration() {
}

public synchronized void registerConfig(String applicationName, String address, String interfaceName, String version) {
if (applicationConfigMap.get(applicationName) == null) {
ApplicationConfig application = new ApplicationConfig();
application.setName(applicationName);
application.setQosEnable(false);
applicationConfigMap.put(applicationName, application);
}

if (registryConfigMap.get(applicationName) == null) {
RegistryConfig registry = new RegistryConfig();
registry.setAddress(address);
registry.setRegister(false);
registryConfigMap.put(applicationName, registry);
}

if (referenceConfigMap.get(interfaceName) == null) {
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
reference.setInterface(interfaceName);
reference.setVersion(version);
reference.setGeneric("true");
referenceConfigMap.put(interfaceName, reference);
}
}

public ApplicationConfig getApplicationConfig(String applicationName) {
return applicationConfigMap.get(applicationName);
}

public RegistryConfig getRegistryConfig(String applicationName) {
return registryConfigMap.get(applicationName);
}

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) {
httpStatementMap.put(httpStatement.getUri(), httpStatement);
}

public HttpStatement getHttpStatement(String uri) {
return httpStatementMap.get(uri);
}

public Executor newExecutor(Connection connection) {
return new SimpleExecutor(this, connection);
}

public boolean authValidate(String uId, String token) {
return auth.validate(uId, token);
}
}

GatewayServerHandler 前置解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package cn.ray.gateway.socket.handlers;

import cn.ray.gateway.bind.IGenericReference;
import cn.ray.gateway.mapping.HttpStatement;
import cn.ray.gateway.session.Configuration;
import cn.ray.gateway.session.GatewaySession;
import cn.ray.gateway.session.defaults.DefaultGatewaySessionFactory;
import cn.ray.gateway.socket.BaseHandler;
import cn.ray.gateway.socket.agreement.AgreementConstants;
import cn.ray.gateway.socket.agreement.GatewayResult;
import cn.ray.gateway.socket.agreement.RequestParser;
import cn.ray.gateway.socket.agreement.ResponseParser;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
* @author Ray
* @date 2023/5/11 15:55
* @description 协议解析处理器
*/
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
HttpStatement httpStatement = configuration.getHttpStatement(uri);
channel.attr(AgreementConstants.HTTP_STATEMENT).set(httpStatement);

// 3、传递给下一个 handler
request.retain();
ctx.fireChannelRead(request);
} catch (Exception e) {
// 4. 封装返回结果
DefaultFullHttpResponse response = new ResponseParser().parse(GatewayResult.buildError(AgreementConstants.ResponseCode._500.getCode(), "网关协议调用失败!" + e.getMessage()));
channel.writeAndFlush(response);
}
}
}

AuthorizationHandler 接口鉴权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package cn.ray.gateway.socket.handlers;

import cn.ray.gateway.mapping.HttpStatement;
import cn.ray.gateway.session.Configuration;
import cn.ray.gateway.socket.BaseHandler;
import cn.ray.gateway.socket.agreement.AgreementConstants;
import cn.ray.gateway.socket.agreement.GatewayResult;
import cn.ray.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;

/**
* @author Ray
* @date 2023/5/19 21:58
* @description 鉴权
*/
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());
HttpStatement httpStatement = channel.attr(AgreementConstants.HTTP_STATEMENT).get();
try {
if (httpStatement.isAuth()) {
try {
String uId = request.headers().get("uId");
String token = request.headers().get("token");
// 鉴权判断
if (null == token || "".equals(token)) {
DefaultFullHttpResponse response = ResponseParser.parse(GatewayResult.buildError(AgreementConstants.ResponseCode._400.getCode(), "对不起,你的 token 不合法!"));
channel.writeAndFlush(response);
}
// 鉴权处理;shiro + jwt
boolean status = configuration.authValidate(uId, token);
// 鉴权成功;直接放行
if (status) {
request.retain();
ctx.fireChannelRead(request);
}
// 鉴权失败
else {
DefaultFullHttpResponse response = ResponseParser.parse(GatewayResult.buildError(AgreementConstants.ResponseCode._403.getCode(), "对不起,你无权访问此接口!"));
channel.writeAndFlush(response);
}
} catch (Exception e) {
DefaultFullHttpResponse response = ResponseParser.parse(GatewayResult.buildError(AgreementConstants.ResponseCode._403.getCode(), "对不起,你的鉴权不合法!"));
channel.writeAndFlush(response);
}
} else {
// 不鉴权直接放行
request.retain();
ctx.fireChannelRead(request);
}
} catch (Exception e) {
// 4. 封装返回结果
DefaultFullHttpResponse response = ResponseParser.parse(GatewayResult.buildError(AgreementConstants.ResponseCode._500.getCode(), "网关协议调用失败!" + e.getMessage()));
channel.writeAndFlush(response);
}
}
}

GatewayAuthorizingRealm 自定义 Shiro 验证领域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package cn.ray.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;

/**
* @author Ray
* @date 2023/5/18 15:14
* @description 验证领域
*/
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 authenticationToken) throws AuthenticationException {
try {
Claims claims = JwtUtil.decode(((GatewayAuthorizingToken) authenticationToken).getJwt());
// 验证签发人是否匹配
if (!authenticationToken.getPrincipal().equals(claims.getSubject()))
throw new AuthenticationException("无效令牌");
} catch (Exception e) {
throw new AuthenticationException("无效令牌");
}
return new SimpleAuthenticationInfo(authenticationToken.getPrincipal(), authenticationToken.getCredentials(), this.getName());
}
}

这里的鉴权逻辑是验证 token 签发人是否一致,即生成 token 时指定的 Subject (签发人,类似userld.userName)与 POST 请求时 Headers 中携带的 uId 是否一致,确保获取 token 的用户和使用 token 的用户是同一个,鉴权才通过。

JwtUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package cn.ray.gateway.authorization;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
* @author Ray
* @date 2023/5/18 14:51
* @description JWT(JSON Web Tokens)https://jwt.io/
*/
public class JwtUtil {

private static final String signingKey = "B*B^5Fe";

/**
* 生成 JWT Token 字符串
* @param issuer 签发人
* @param ttlMillis 有效期
* @param claims 额外信息
* @return Token
*/
public static String encode(String issuer, long ttlMillis, Map<String, Object> claims) {
if (null == claims) {
claims = new HashMap<>();
}

// 签发时间(iat):荷载部分的标准字段之一
long currentMillis = System.currentTimeMillis();
Date currentTime = new Date(currentMillis);

JwtBuilder jwtBuilder = Jwts.builder()
// 荷载部分
.setClaims(claims)
// 签发时间
.setIssuedAt(currentTime)
// 签发人:类似 userId、userName
.setSubject(issuer)
// 设置生成签名的算法和秘钥
.signWith(SignatureAlgorithm.HS256, signingKey);

if (ttlMillis>=0) {
long expMillis = currentMillis + ttlMillis;
Date expTime = new Date(expMillis);
// 过期时间(exp):荷载部分的标准字段之一,代表这个 JWT 的有效期。
jwtBuilder.setExpiration(expTime);
}

return jwtBuilder.compact();
}

public static Claims decode(String token) {
return Jwts.parser()
// 设置签名的秘钥
.setSigningKey(signingKey)
// 设置需要解析的 jwt
.parseClaimsJws(token)
.getBody();
}
}

ProtocolDataHandler 泛化调用处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package cn.ray.gateway.socket.handlers;

import cn.ray.gateway.bind.IGenericReference;
import cn.ray.gateway.executor.result.SessionResult;
import cn.ray.gateway.session.GatewaySession;
import cn.ray.gateway.session.defaults.DefaultGatewaySessionFactory;
import cn.ray.gateway.socket.BaseHandler;
import cn.ray.gateway.socket.agreement.AgreementConstants;
import cn.ray.gateway.socket.agreement.GatewayResult;
import cn.ray.gateway.socket.agreement.RequestParser;
import cn.ray.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;

import java.util.Map;

/**
* @author Ray
* @date 2023/5/20 21:27
* @description 协议消息处理
*/
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 (null == uri) return;
Map<String, Object> args = requestParser.parse();

// 2. 调用会话服务
GatewaySession gatewaySession = gatewaySessionFactory.openSession(uri);
IGenericReference reference = gatewaySession.getMapper();
SessionResult result = reference.$invoke(args);

// 3. 封装返回结果
DefaultFullHttpResponse response = ResponseParser.parse("0000".equals(result.getCode()) ? GatewayResult.buildSuccess(result.getData()) : GatewayResult.buildError(AgreementConstants.ResponseCode._404.getCode(), "网关协议调用失败!"));
channel.writeAndFlush(response);
} catch (Exception e) {
// 4. 封装返回结果
DefaultFullHttpResponse response = ResponseParser.parse(GatewayResult.buildError(AgreementConstants.ResponseCode._502.getCode(), "网关协议调用失败!" + e.getMessage()));
channel.writeAndFlush(response);
}
}
}

AgreementConstants 协议参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package cn.ray.gateway.socket.agreement;

import cn.ray.gateway.mapping.HttpStatement;
import io.netty.util.AttributeKey;

/**
* @author Ray
* @date 2023/5/19 21:52
* @description 协议参数
*/
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;
}
}

}

GatewayResult 网关结果封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package cn.ray.gateway.socket.agreement;

/**
* @author Ray
* @date 2023/5/19 21:55
* @description 网关结果封装
*/
public class GatewayResult {

private String code;
private String message;
private Object data;

protected GatewayResult(String code, String info, Object data) {
this.code = code;
this.message = info;
this.data = data;
}

public static GatewayResult buildSuccess(Object data) {
return new GatewayResult(AgreementConstants.ResponseCode._200.getCode(), AgreementConstants.ResponseCode._200.getInfo(), data);
}

public static GatewayResult buildError(String code, String info) {
return new GatewayResult(code, info, null);
}

public String getCode() {
return code;
}

public String getMessage() {
return message;
}

public Object getData() {
return data;
}
}

测试

RPC 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.ray.gateway.interfaces;

import cn.ray.gateway.rpc.IActivityBooth;
import cn.ray.gateway.rpc.dto.XReq;
import com.alibaba.fastjson.JSON;
import org.apache.dubbo.config.annotation.Service;

/**
* @author Ray
* @date 2023/5/12 22:36
* @description
*/
@Service(version = "1.0.0")
public class ActivityBooth implements IActivityBooth {

@Override
public String sayHi(String str) {
return "hi " + str + " by api-gateway-test";
}

@Override
public String insert(XReq req) {
return "hi " + JSON.toJSONString(req) + " by api-gateway-test";
}

@Override
public String test(String str, XReq req) {
return "hi " + str + JSON.toJSONString(req) + " by api-gateway-test";
}
}

启动网关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package cn.ray.gateway.test;

import cn.ray.gateway.mapping.HttpRequestType;
import cn.ray.gateway.mapping.HttpStatement;
import cn.ray.gateway.session.Configuration;
import cn.ray.gateway.session.defaults.DefaultGatewaySessionFactory;
import cn.ray.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;

/**
* @author Ray
* @date 2023/5/11 16:26
* @description
*/
public class ApiTest {

private final Logger logger = LoggerFactory.getLogger(ApiTest.class);

@Test
public void test_GenericReference() throws InterruptedException, ExecutionException {
// 1. 创建配置信息加载注册
Configuration configuration = new Configuration();
configuration.registerConfig("api-gateway-test","zookeeper://127.0.0.1:2181","cn.ray.gateway.rpc.IActivityBooth", "1.0.0");

HttpStatement httpStatement01 = new HttpStatement(
"api-gateway-test",
"cn.ray.gateway.rpc.IActivityBooth",
"sayHi",
"/wg/activity/sayHi",
HttpRequestType.GET,
"java.lang.String",
false);

HttpStatement httpStatement02 = new HttpStatement(
"api-gateway-test",
"cn.ray.gateway.rpc.IActivityBooth",
"insert",
"/wg/activity/insert",
HttpRequestType.POST,
"cn.ray.gateway.rpc.dto.XReq",
true);

configuration.addMapper(httpStatement01);
configuration.addMapper(httpStatement02);

// 2. 基于配置构建会话工厂
DefaultGatewaySessionFactory gatewaySessionFactory = new DefaultGatewaySessionFactory(configuration);

// 3. 创建启动网关网络服务
GatewaySocketServer server = new GatewaySocketServer(configuration, gatewaySessionFactory);

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

测试结果

GET 不鉴权

8-测试-1

POST 鉴权

JWT 生成 token 时指定的签发人与当前 Headers 中的 uId 不一致,也即 token 与 uId 不对应。

8-测试-2

JWT 生成 token 时指定的签发人与当前 Headers 中的 uId 一致。

8-测试-3

总结

8-总结-1

总算理清了 Cglib 代理流程:

  1. intercept 方法是CGLIB库中的拦截器接口 MethodInterceptor 的方法,在代码中被重写以实现自定义的拦截逻辑。该方法会拦截代理对象的所有方法调用,包括代理对象的父类方法和接口方法。
  2. intercept 方法是拦截器的核心方法,用于拦截目标方法的调用并进行处理。它接收以下参数:

    1. o:代理对象。
    2. method:目标方法的反射对象。
    3. objects:目标方法的参数数组。
    4. methodProxy:用于调用目标方法的方法代理对象。
      • MethodProxy 是CGLIB库提供的一个方法代理对象,用于调用目标方法。
      • 在CGLIB代理中,当拦截器的 intercept 方法被调用时,可以使用 MethodProxy 对象来调用目标方法,而不是直接通过反射调用目标方法。这样做的好处是可以绕过反射的性能开销,提高方法调用的效率。
      • 具体来说,MethodProxyinvokeSuper(Object obj, Object[] args) 方法可以用来调用目标方法。它接受两个参数:
        • obj:目标方法所属的对象,即代理对象。
        • args:目标方法的参数数组。
        • 通过调用 invokeSuper 方法,可以在拦截器中将方法调用传递给目标方法。这样就可以在拦截器中实现自定义的逻辑,并在适当的时机调用目标方法。
    5. 通过调用 invokeSuper 方法,可以在拦截器中将方法调用传递给目标方法。这样就可以在拦截器中实现自定义的逻辑,并在适当的时机调用目标方法。
    6. 总结来说,MethodProxy 提供了一种高效的方式来调用目标方法,避免了反射带来的性能开销,并且可以与拦截器协同工作,实现代理对象方法的拦截和处理。
  3. 在这段代码中,intercept 方法被用作代理对象的拦截器,它会拦截目标方法的调用并进行处理。具体来说,它拦截的是代理对象中任何被调用的方法,不限于某一个具体的方法。

  4. 当代理对象的方法被调用时,CGLIB库会将该调用传递给 intercept 方法,在该方法内部可以实现自定义的拦截逻辑。在代码中,intercept 方法使用了 MapperMethod 对象来表示要执行的映射方法,并调用了该方法的 execute 方法来执行映射逻辑。
  5. 总之,intercept 方法在这里作为一个通用的拦截器,用于处理代理对象的所有方法调用,并在其中实现特定的逻辑。

如果你想要在 intercept 方法中只拦截指定的方法,可以在方法内部添加条件判断逻辑。以下是一个示例,演示如何只拦截指定方法的调用:

1
2
3
4
5
6
7
8
9
10
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("methodName")) {
// 对指定方法进行拦截逻辑处理
// ...
}

// 对于其他方法,不进行拦截,直接调用目标方法
return methodProxy.invokeSuper(o, objects);
}

在上述示例中,我们使用了 method.getName().equals("methodName") 条件判断来判断目标方法的名称是否为我们希望拦截的方法名。如果是,可以在条件判断的分支中添加自定义的拦截逻辑。对于其他方法,我们直接调用 methodProxy.invokeSuper(o, objects) 来调用目标方法,实现不拦截的效果。

网关启动 Netty 服务

8-总结-2

全流程

8-总结-3

权限认证组件 Shiro + JWT

8-总结-4