编程知识 cdmana.com

【SpringCloud】Spring Cloud Gateway实现接口防篡改

Spring Cloud Gateway实现接口防篡改和防重放

Spring Cloud Gateway 是基于 Spring Framework 5.0 和 Spring Boot 2.0 构建的 API 网关,提供路由等功能。其旨在提供一种简单而有效的方法路由到 API,并为它们提供跨领域的关注点,例如:安全性、监视 / 指标和弹性。



前言

.在云计算、大数据等技术日趋成熟的情况下,微服务架构逐渐进入人们的视线,微服务架构的本质是把整体的业务拆分成特定明确功能的服务,在分布式环境下,随着微服务架构的广泛应用,各个服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务,这些服务之间的调用非常复杂,为了配合新核心建设,保障系统间系统传递的安全性,需要设计报文防篡改的技术。


一、防篡改是什么?

防篡改(英语:Tamper resistance)是指通过包装、系统或其他物理措施抗击产品正常用户的篡改(tamper,故意引发故障或造成破坏)的行为。

二、防重放是什么?

入侵者 C 可以从网络上截获 A 发给 B 的报文。C 并不需要破译这个报文(因为这
可能很花很多时间)而可以直接把这个由 A 加密的报文发送给 B,使 B 误认为 C 就是 A。然后
B 就向伪装是 A 的 C 发送许多本来应当发送给 A 的报文

三.防篡改和防重放的解决方式

  1. 可以通过时间戳,将时间戳放在header头中进行处理
  2. 通过时间戳 + sign签名处理,通过将报文参数进行相应的md5进行签名处理,同时将时间戳和sign放在header中,网关进行相应的验签证明请求的合法性
  3. 通过时间戳+随机数(norce)+sign签名的方式进行处理
    流程如下:
    在这里插入图片描述
  • 用户发送请求,带上时间戳,随机数和sign数据
  • 请求到网关,网关校验相关的数据
  • 如果校验失败是直接返回,校验成功就跳转到后台的业务系统服务中

四.Spring Cloud Gateway实现

Spring Cloud Gateway是通过webflux实现的,我们现在进行代码coding

创建过滤器SignatureAntiReplayFilter

代码如下:

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

    @Resource
    private ReactiveCredentialsFilter reactiveCredentialsFilter;
	//设置一个开关,进行控制是否开启报文签名校验
    @Value("${gateway.openSign:#{true}}")
    private Boolean openSign;

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
    
        log.info("===========签名校验过滤器======");
       	/*** ** * 这里面还可以添加其他的业务逻辑在里面 */
        //如果不开启的话就直接放行
        if (Boolean.FALSE.equals(openSign)) {
    
            return webFilterChain.filter(serverWebExchange);
        }
        return SignatureAntiReplayMono.mono(serverWebExchange, webFilterChain);
    }

    @Override
    public int getOrder() {
    
        return -2;
    }
}

创建SignatureAntiReplayMono处理类

代码如下:

@Slf4j
public class SignatureAntiReplayMono {
    

    private SignatureAntiReplayMono() {
    
    }

    /** * 处理过滤器 * * @param serverWebExchange serverWebExchange * @param webFilterChain 过滤器链 * @return 返回mono */
    public static Mono<Void> mono(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
    
        ServerHttpResponse serverHttpResponse = serverWebExchange.getResponse();
        ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
        HttpHeaders httpHeaders = serverWebExchange.getRequest().getHeaders();
        List<String> contentType = httpHeaders.get(HttpHeaders.CONTENT_TYPE);
        if (!CollectionUtils.isEmpty(contentType) && !contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
    
            Result<Void> checkMap = AccessRequestCheck.checkParam(serverWebExchange.getRequest().getHeaders());
            if (Objects.equals(String.valueOf(HttpStatus.OK.value()), checkMap.getCode())) {
    
                return webFilterChain.filter(serverWebExchange);
            } else {
    
                try {
    
                    return serverHttpResponse.writeWith(Flux.just(serverHttpResponse.bufferFactory().wrap(ObjectMapperUtil.getObjectMapper().writeValueAsString(checkMap).getBytes(StandardCharsets.UTF_8))));
                } catch (JsonProcessingException e) {
    
                    return Mono.empty();
                }
            }
        }
        ServerRequest serverRequest = ServerRequest.create(serverWebExchange, HandlerStrategies.withDefaults().messageReaders());
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(originalBody -> modifyBody()
                .apply(serverWebExchange, originalBody));
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(serverWebExchange.getRequest().getHeaders());
        headers.remove("Content-Length");
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        CusCachedBodyOutputMessage outputMessage = new CusCachedBodyOutputMessage(serverWebExchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
    
            ServerHttpRequest decorator = decorate(serverWebExchange, headers, outputMessage);
            return webFilterChain.filter(serverWebExchange.mutate().request(decorator).build());
        })).onErrorResume((throwable) -> {
    
            return release(outputMessage, (Throwable) throwable);
        });
    }

    protected static Mono<Void> release(CusCachedBodyOutputMessage outputMessage, Throwable throwable) {
    
        return Boolean.TRUE.equals(outputMessage.isCached()) ? outputMessage.getBody().map(DataBufferUtils::release).then(Mono.error(throwable)) : Mono.error(throwable);
    }

    static ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CusCachedBodyOutputMessage outputMessage) {
    
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
    
            public HttpHeaders getHeaders() {
    
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(headers);
                if (contentLength > 0L) {
    
                    httpHeaders.setContentLength(contentLength);
                } else {
    
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }
                return httpHeaders;
            }

            public Flux<DataBuffer> getBody() {
    
                return outputMessage.getBody();
            }
        };
    }

    private static BiFunction<ServerWebExchange, String, Mono<String>> modifyBody() {
    
        return (serverWebExchange, raw) -> {
    
            try {
    
                if (StringUtils.isBlank(raw)) {
    
                    raw = "{}";
                }
                Result<Void> checkMap = AccessRequestCheck.checkParam(serverWebExchange.getRequest().getHeaders(), raw);
                if (Objects.equals(String.valueOf(HttpStatus.OK.value()), checkMap.getCode())) {
    
                    return Mono.just(raw);
                } else {
    
                    return Mono.empty();
                }
            } catch (Exception e) {
    
                return Mono.empty();
            }
        };
    }
}

创建相关的AccessRequestCheck类

代码如下:

 /** * 请求过期时间 */
    public static final long EXPIRE_TIME = 60000;

    /** * 时间戳属性 */
    public static final String TIMESTAMP_PROPERTIES = "timestamp";

    /** * 签名属性 */
    public static final String SIGN_PROPERTIES = "sign";

    /** * 盐 */
    public static final String NORCE_PROPERTIES = "norce";


    public static StringRedisTemplate redisTemplate;

    @Autowired
    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
    
        this.redisTemplate = redisTemplate;
    }

    public static Result<Void> checkParam(HttpHeaders headers, String body) {
    
        Result<Void> result = new Result<>();
        //获取header中的时间戳
        List<String> timestamp = headers.get(TIMESTAMP_PROPERTIES);
        //获取uuid 这里设置为盐
        List<String> uuid = headers.get(NORCE_PROPERTIES);
        //从header头中添加签名,签名使用请求参数进行加密生成sign
        List<String> sign = headers.get(SIGN_PROPERTIES);
        if (CollectionUtils.isEmpty(timestamp)) {
    
            result.setCode(ErrorCodeEnum.TIMESTAMP_PARAM_MISSING.getCode());
            result.setMsg(ErrorCodeEnum.TIMESTAMP_PARAM_MISSING.getMsg());
            return result;
        }
        if (CollectionUtils.isEmpty(uuid)) {
    
            result.setCode(ErrorCodeEnum.NORCE_PROPERTIES.getCode());
            result.setMsg(ErrorCodeEnum.NORCE_PROPERTIES.getMsg());
            return result;
        }
        if (CollectionUtils.isEmpty(sign)) {
    
            result.setCode(ErrorCodeEnum.SIGN_PARAM_MISSING.getCode());
            result.setMsg(ErrorCodeEnum.SIGN_PARAM_MISSING.getMsg());
            return result;
        }
        //判断请求是否过期
        if (!checkExpire(Long.parseLong(timestamp.get(0)))) {
    
            result.setCode(ErrorCodeEnum.REQUEST_EXPIRE.getCode());
            result.setMsg(ErrorCodeEnum.REQUEST_EXPIRE.getMsg());
            return result;
        }
        //判断是否重复请求
        if (Objects.nonNull(redisTemplate.opsForValue().get(uuid.get(0)))) {
    
            result.setCode(ErrorCodeEnum.REQUEST_REPLACE.getCode());
            result.setMsg(ErrorCodeEnum.REQUEST_REPLACE.getMsg());
            return result;
        }
        //校验签名
        if (!SignUtil.verifySign(body, sign.get(0), uuid.get(0))) {
    
            result.setCode(ErrorCodeEnum.SIGN_ERROR.getCode());
            result.setMsg(ErrorCodeEnum.SIGN_ERROR.getMsg());
            return result;
        }

        redisTemplate.opsForValue().set(uuid.get(0), sign.get(0), EXPIRE_TIME, TimeUnit.MILLISECONDS);
        result.setCode(String.valueOf(HttpStatus.OK.getCode()));
        result.setMsg(HttpStatus.OK.getMessageEn());
        return result;
    }

    /** * 对时间戳和uuid进行校验 * * @param headers 请求头 * @return */
    public static Result<Void> checkParam(HttpHeaders headers) {
    
        Result<Void> result = new Result<>();
        //获取header中的时间戳
        List<String> timestamp = headers.get(TIMESTAMP_PROPERTIES);
        //获取uuid 这里设置为盐
        List<String> uuid = headers.get(UUID_PROPERTIES);
        if (CollectionUtils.isEmpty(timestamp)) {
    
            result.setCode(ErrorCodeEnum.TIMESTAMP_PARAM_MISSING.getCode());
            result.setMsg(ErrorCodeEnum.TIMESTAMP_PARAM_MISSING.getMsg());
            return result;
        }
        if (CollectionUtils.isEmpty(uuid)) {
    
            result.setCode(ErrorCodeEnum.NORCE_PROPERTIES.getCode());
            result.setMsg(ErrorCodeEnum.NORCE_PROPERTIES.getMsg());
            return result;
        }
        //判断请求是否过期
        if (!checkExpire(Long.parseLong(timestamp.get(0)))) {
    
            result.setCode(ErrorCodeEnum.REQUEST_EXPIRE.getCode());
            result.setMsg(ErrorCodeEnum.REQUEST_EXPIRE.getMsg());
            return result;
        }
        //判断是否重复请求
        if (Objects.nonNull(redisTemplate.opsForValue().get(uuid.get(0)))) {
    
            result.setCode(ErrorCodeEnum.REQUEST_REPLACE.getCode());
            result.setMsg(ErrorCodeEnum.REQUEST_REPLACE.getMsg());
            return result;
        }

        redisTemplate.opsForValue().set(uuid.get(0), uuid.get(0), EXPIRE_TIME, TimeUnit.MILLISECONDS);
        result.setCode(String.valueOf(HttpStatus.OK.getCode()));
        result.setMsg(HttpStatus.OK.getMessageEn());
        return result;
    }

    /** * 检测请求时间是否过期 * * @param requestTime 请求时间 * @return 返回检测结果 */
    private static boolean checkExpire(long requestTime) {
    
        return EXPIRE_TIME > System.currentTimeMillis() - requestTime;
    }

版权声明
本文为[Tony-devj]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qian1314520hu/article/details/126177811

Scroll to Top