返回

责任链实现网关统一校验

发布时间:2023-05-07 14:51:10 119
# java# springboot# git# 软件# 软件

一、Commons Chain 介绍

Chain Of Responsibility(COR)模式也叫职责链模式或者职责连锁模式,是由GoF提出的23种软件设计模式的一种。Chain of Responsibility模式是行为模式之一,该模式构造一系列分别担当不同的职责的类的对象来共同完成一个任务,这些类的对象之间像链条一样紧密相连,所以被称作职责链模式。

Apache Commons Chain 提供了对COR模式的基础支持,简化和促进了实际应用CoR模式。CommonsChain实现了Chain of Responsebility和Command模式,其中的springboot配置方式使得调用方和Command的实现方的耦合度大大的降低,提高了灵活性。

二、Apache Commons Chain 核心组件

 

责任链实现网关统一校验_List

1、 Context接口

Context表示命令执行的上下文,在命令间实现共享信息的传递。 extends Map,父接口是Map,它只是一个标记接口。ContextBase实现了Context。对于web环境,可以使用WebContext类及其子类(FacesWebContext、PortletWebContext和ServletWebContext)。

2、Command接口

Commons Chain中最重要的接口,表示在Chain中的具体某一步要执行的命令。它只有一个方法:boolean execute(Context context)。如果返回true,那么表示Chain的处理结束,Chain中的其他命令不会被调用;返回false,则Chain会继续调用下一个Command,直到:

Command返回true;
Command抛出异常;
Chain的末尾;


3、Chain接口

它表示“命令链”,chain of command,要在其中执行的命令,需要先添加到Chain中,父接口是Command , ChainBase实现了它。

4、Filter接口

extends Command,它是一种特殊的Command。除了Command的execute方法之外,还包括了一个方法:boolean postProcess(Context context, Exception exception)。Commons Chain会在执行了Filter的execute方法之后,执行postprocess(不论Chain以何种方式结束)。Filter的执行execute的顺序与Filter出现在Chain中出现的位置一致,但是执行postprocess顺序与之相反。如:如果连续定义了filter1和filter2,那么execute的执行顺序是:filter1 -> filter2;而postprocess的执行顺序是:filter2 -> filter1。


三、Commons Chain 基本使用

   现在,我们模拟网关自定义校验方式看一下chain是工作实现。分为:​校验actutor端点​​黑名单校验​​应用限流校验​​。3个工作类如下:

1.com.open.capacity.gateway.chain.ActuatorCommand

package com.open.capacity.gateway.chain;

import java.net.URI;
import java.util.List;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import com.google.common.collect.Lists;
import com.open.capacity.common.dto.ResponseEntity;
import com.open.capacity.common.properties.SecurityProperties;
import com.open.capacity.gateway.context.SecurityContext;

import cn.hutool.http.HttpStatus;

/**
*
* 端点命令 禁止通过网关访问服务的端点
*/
@Component
public class ActuatorCommand implements Command {

@Autowired
private SecurityProperties securityProperties;

private static final List list = Lists.newArrayList();

static {
list.add("/actuator/**");
list.add("/*/actuator/**");
}

@Override
public boolean execute(Context context) throws Exception {

if (!securityProperties.getActuator().getEnable()) {
SecurityContext securityContext = (SecurityContext) context;
ServerHttpRequest request = securityContext.getExchange().getRequest();
URI uri = request.getURI();
String path = uri.getPath();
AntPathMatcher antPathMatcher = new AntPathMatcher();
boolean matchActuator = list.stream().anyMatch(item -> antPathMatcher.match(item, path));
if (matchActuator) {
securityContext.setCode(HttpStatus.HTTP_UNAUTHORIZED);
securityContext.setEntity(ResponseEntity.failed("禁止通过网关访问服务端点!"));
securityContext.setResult(true);
return true;
}
}

return false;
}

}

2.com.open.capacity.gateway.chain.BlackListCommand

package com.open.capacity.gateway.chain;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.List;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import com.open.capacity.common.dto.ResponseEntity;
import com.open.capacity.common.properties.BlackListProperties;
import com.open.capacity.common.utils.IpAddressMatcher;
import com.open.capacity.gateway.context.SecurityContext;

import cn.hutool.http.HttpStatus;

/**
*
* 黑名单命令 禁止黑名单用户范围
*/
@Component
public class BlackListCommand implements Command {

@Autowired
private BlackListProperties blackListProperties;

@Override
public boolean execute(Context context) throws Exception {

if (blackListProperties.getEnable()) {
SecurityContext securityContext = (SecurityContext) context;
ServerHttpRequest request = securityContext.getExchange().getRequest();
URI uri = request.getURI();
String path = uri.getPath();
InetSocketAddress remoteAddress = request.getRemoteAddress();
if (remoteAddress == null) {
securityContext.setCode(HttpStatus.HTTP_UNAUTHORIZED);
securityContext.setEntity(ResponseEntity.failed("当前IP已被禁用!"));
securityContext.setResult(true);
return true;
}
InetAddress address = remoteAddress.getAddress();
String hostAddress = address.getHostAddress();
List ipList = blackListProperties.getIpList();
for (String ip : ipList) {
IpAddressMatcher ipAddressMatcher = new IpAddressMatcher(ip);
boolean matches = ipAddressMatcher.matches(hostAddress);
if (matches) {
securityContext.setCode(HttpStatus.HTTP_UNAUTHORIZED);
securityContext.setEntity(ResponseEntity.failed("当前IP已被禁用!"));
securityContext.setResult(true);
return true;
}
}
AntPathMatcher antPathMatcher = new AntPathMatcher();
List services = blackListProperties.getServices();
for (BlackListProperties.BlackList service : services) {
String name = service.getName();
List pathList = service.getPathList();
for (String p : pathList) {
String pattern = name.startsWith("/") ? name : "/" + name;
pattern = p.startsWith("/") ? pattern + p : pattern + "/" + p;
boolean match = antPathMatcher.match(pattern, path);
if (match) {
securityContext.setCode(HttpStatus.HTTP_UNAUTHORIZED);
securityContext.setEntity(ResponseEntity.failed("当前IP访问的目标地址已被禁用!"));
securityContext.setResult(true);
return true;
}
}
}
}

return false;
}

}

3.com.open.capacity.gateway.chain.RateLimitCommand

package com.open.capacity.gateway.chain;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import com.open.capacity.common.dto.ResponseEntity;
import com.open.capacity.common.properties.SecurityProperties;
import com.open.capacity.gateway.context.SecurityContext;
import com.open.capacity.gateway.service.IRateLimitService;
import com.open.capacity.gateway.utils.TokenUtil;

/**
* 限流命令
*/
@Component
public class RateLimitCommand implements Command {

@Autowired
private IRateLimitService rateLimitService;

@Autowired
private SecurityProperties securityProperties;

@Override
public boolean execute(Context context) throws Exception {

if (securityProperties.getRatelimit().getEnable()) {
SecurityContext securityContext = (SecurityContext) context;
ServerHttpRequest request = securityContext.getExchange().getRequest();
String accessToken = TokenUtil.extractToken(request);
String reqUrl = request.getPath().value();
// 超额自增处理
boolean flag = rateLimitService.checkRateLimit(reqUrl, accessToken);
// 超额限流
if (flag) {
securityContext.setCode(HttpStatus.TOO_MANY_REQUESTS.value());
securityContext.setEntity(ResponseEntity.failed("TOO MANY REQUESTS!"));
securityContext.setResult(true);
return true;
}
}

return false;
}

}


4.com.open.capacity.gateway.chain.SecurityFilterChain

 

package com.open.capacity.gateway.chain;

import javax.annotation.Resource;

import org.apache.commons.chain.impl.ChainBase;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class SecurityFilterChain extends ChainBase implements InitializingBean {

@Resource
private ActuatorCommand actuatorCommand;
@Resource
private BlackListCommand blackListCommand;
@Resource
private RateLimitCommand rateLimitCommand;

@Override
public void afterPropertiesSet() throws Exception {
// 将请求处理者角色加入链中
addCommand(actuatorCommand);
addCommand(blackListCommand);
addCommand(rateLimitCommand);

}

}


5.com.open.capacity.gateway.filter.SecurityChainFilter

package com.open.capacity.gateway.filter;

import javax.annotation.Resource;

import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;

import com.open.capacity.common.utils.WebfluxResponseUtil;
import com.open.capacity.gateway.chain.SecurityFilterChain;
import com.open.capacity.gateway.context.SecurityContext;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class SecurityChainFilter implements WebFilter, Ordered {

@Resource
private SecurityFilterChain securityFilterChain;

/**
* 最低优先级(最大值):0 大于 0 无效
*/
public static final int ORDERED = Ordered.HIGHEST_PRECEDENCE + 10000;

@Setter
private int order = ORDERED;

@Override
public int getOrder() {
return order;
}

@NonNull
@Override
public Mono filter(ServerWebExchange exchange, @NonNull WebFilterChain chain) {

// 封装请求对象
SecurityContext securityContext = new SecurityContext();
securityContext.setExchange(exchange);
try {
securityFilterChain.execute(securityContext);
} catch (Exception e) {
if (log.isTraceEnabled()) {
log.trace(" security chains exception :{}", e.getMessage());
}
}
// 获取处理结果
if (securityContext.isResult()) {
return WebfluxResponseUtil.responseWrite(exchange, securityContext.getCode(), securityContext.getEntity());
} else {
return chain.filter(exchange);
}

}

}

总结: Commons Chain实现了Chain of Responsebility和Command模式,Springboot配置方式使得调用方和Command的实现方的耦合度大大的降低,提高了灵活性。 


项目地址:

​​https://github.com/owenwangwen/open-capacity-platform​​

特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线