过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。
业务场景:每次请求通过网关,需要验证请求头是否携带 token,sign签名等
类图:
AuthService:所有滤器类都必须实现的接口
AuthTokenServiceImpl:Token验证过滤器
AuthSignServiceImpl:签名验证过滤器
AuthFactory:过滤器工厂,利用SpringBoot功能特性,实现自动获取过滤器
AuthDTO:过滤器所需要的参数
AuthGatewayFilterFactory:权限校验过滤器(gateway)
AuthService:
/**
* @Author: wmh
* @Description: 权限校验过滤器
* @Date: 2023/8/3 18:19
* @Version: 1.0
*/
public interface AuthService {
/**
* @Description: 过滤方法
* @Param authDTO: 网关上下文
* @return: String
* @Author: wmh
* @Date: 2023/8/3 18:12
*/
String apply(AuthDTO authDTO);
}
返回值可以定义为统一返回值(R)等,为了演示方便,就返回字符串了
AuthTokenServiceImpl:
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
/**
* @Author: wmh
* @Description: token校验
* @Date: 2023/8/3 18:21
* @Version: 1.0
*/
@Slf4j
@Order(0)
@Service
public class AuthTokenServiceImpl implements AuthService {
/**
* @Description: 验证token
* @Param authDTO: 网关上下文
* @return: com.norinaviation.atm.common.base.data.R
* @Author: wmh
* @Date: 2023/8/3 19:31
*/
@Override
@SneakyThrows
public String apply(AuthDTO authDTO) {
String tokenHeader = authDTO.getHeaders().getFirst(CommonConstant.X_TOKEN);
if (StrUtil.isBlank(appId)) {
return "appId不能为空";
}
if (StrUtil.isBlank(tokenHeader)) {
return "TOKEN不能为空";
}
JWT jwt = JWTUtil.parseToken(tokenHeader);
boolean verifyKey = jwt.setKey(CommonConstant.JWT_TOKEN.getBytes()).verify();
// 验证token是否正确
if (!verifyKey) {
log.info("appId:{}, TOKEN auth fail, TOKEN:{}", appId, tokenHeader);
return "TOKEN认证失败";
}
boolean verifyTime = jwt.validate(0);
// 验证token是否过期
if (!verifyTime) {
log.info("appId:{}, TOKEN expired, TOKEN:{}", appId, tokenHeader);
return "TOKEN已过期";
}
return "success";
}
}
AuthSignServiceImpl:
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
/**
* @Author: wmh
* @Description: 验签校验
* @Date: 2023/8/3 18:24
* @Version: 1.0
*/
@Slf4j
@Order(1)
@Service
public class AuthSignServiceImpl implements AuthService {
/**
* @Description: 验证签名
* @Param authDTO: 网关上下文
* @return: Stirng
* @Author: wmh
* @Date: 2023/8/3 19:30
*/
@Override
@SneakyThrows
public Stirng apply(AuthDTO authDTO) {
// 签名逻辑,业务代码就不公开了
return "success";
}
}
AuthFactory:
import cn.hutool.core.util.ObjectUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @Author: wmh
* @Description: 权限工厂
* @Date: 2023/8/7 15:54
* @Version: 1.0
*/
@Component
public class AuthFactory implements ApplicationContextAware {
/**
* 过滤方式
*/
private List<AuthService> authFilters = new ArrayList<>();
/**
* 获取应用上下文并获取相应的接口实现类
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取实现类
Map<Integer, AuthService> authServiceMap = new HashMap<>();
applicationContext.getBeansOfType(AuthService.class).values().stream().forEach(authService -> {
if (ObjectUtil.isNull(authService.getClass().getAnnotation(Order.class))) {
authServiceMap.put(CommonConstant.DEFAULT_ORDER, authService);
}
else {
authServiceMap.put(authService.getClass().getAnnotation(Order.class).value(), authService);
}
});
// 根据order排序
authServiceMap.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())).forEach(map -> {
authFilters.add(map.getValue());
});
}
/**
* @Description: 是否全部符合过滤条件
* @Param authDTO: 网关上下文
* @return: String
* @Author: wmh
* @Date: 2023/8/3 19:27
*/
public String apply(AuthDTO authDTO) {
for (AuthService filter : authFilters) {
String str = filter.apply(authDTO);
if (!StrUtil.equals(str, "success")) {
return str;
}
}
return "success";
}
}
AuthDTO:
import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @Author: wmh
* @Description: 网关上下文
* @Date: 2023/8/3 19:09
* @Version: 1.0
*/
@Data
public class AuthDTO {
/**
* cache headers
*/
private HttpHeaders headers;
/**
* cache json body
*/
private String cacheBody;
/**
* cache formdata
*/
private MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
}
此类为gateway网关需要,只展示使用过滤链的代码块
AuthGatewayFilterFactory:
/**
* @Author: wmh
* @Description: 权限校验过滤器
* @Date: 2023/8/3 19:15
* @Version: 1.0
*/
@Slf4j
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Autowired
private AuthFactory authFactory;
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
...
// 获取request body
GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
AuthDTO authDTO = new AuthDTO();
authDTO.setHeaders(gatewayContext.getHeaders());
authDTO.setCacheBody(gatewayContext.getCacheBody());
authDTO.setFormData(gatewayContext.getFormData());
// 验证
String strr = authFactory.apply(authDTO);
...
return chain.filter(exchange);
};
}
}
Gateway相关:SpringCloud-Gateway实现网关_springcloud配置网关_W_Meng_H的博客-CSDN博客网关作为流量的入口,常用的功能包括路由转发、权限校验、限流等Spring Cloud 是Spring官方推出的第二代网关框架,由WebFlux+Netty+Reactor实现的响应式的API网关,它不能在传统的servlet容器工作,也不能构建war包。基于Filter的方式提供网关的基本功能,例如说安全认证、监控、限流等。_springcloud配置网关https://blog.csdn.net/W_Meng_H/article/details/129775851
CommonConstant(常量类):
/**
* @Author: wmh
* @Description: 常用变量
* @Date: 2023/3/30 10:29
* @Version: 1.0
*/
@Component
public class CommonConstant {
// JWT密钥
public static String JWT_TOKEN;
// 请求头中的token
public static final String X_TOKEN = "X-TOKEN";
// 请求头中的签名
public static final String X_SIGN = "X-SIGN";
// 请求头中的appId
public static final String X_APPID = "X-APPID";
// 请求头中的时间戳
public static final String X_TIMESTAMP = "X-TIMESTAMP";
}