全网讲的最细最全的接口加密,解密,签名全套教程,附源码

数据签名,主要就是为了防止 数据被 篡改

避免body 只读一次

【视频教程:阿里测开带你30天学完自动化测试从入门到入职极限压缩精品教程_哔哩哔哩_bilibili


@Component
@WebFilter(filterName = "httpServletRequestWrapperFilter", urlPatterns = {"/*"})
public class RepeatReadFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 防止流读取一次后就没有了, 所以需要将流继续写出去
        String contentType = request.getContentType();
        if(StringUtil.isNotEmpty(contentType) && contentType.contains(MediaType.APPLICATION_JSON_VALUE)){
            ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
            chain.doFilter(requestWrapper, response);
        }else {
            chain.doFilter(request,response);
        }


    }

    @Override
    public void destroy() {

    }
}


public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        String sessionStream = getBodyString(request);
        body = sessionStream.getBytes(Charset.forName("UTF-8"));
    }

    /**
     * 获取请求Body
     *
     * @param request
     * @return
     */
    public String getBodyString(final ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (InputStream inputStream = cloneInputStream(request.getInputStream());
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    /**
     * Description: 复制输入流</br>
     *
     * @param inputStream
     * @return</br>
     */
    public InputStream cloneInputStream(ServletInputStream inputStream) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) > -1) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byteArrayOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    }

    @Override
    public BufferedReader getReader() {

        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {

            @Override
            public int read() {

                return bais.read();
            }

            @Override
            public boolean isFinished() {

                return false;
            }

            @Override
            public boolean isReady() {

                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }
}

拦截器

@Slf4j
@Component
public class SignAuthInterceptor implements HandlerInterceptor {

    /**
     * 时间戳
     */
    private static final String TIMESTAMP = "timeStamp";

    /**
     * 应用 appKey
     */
    private static final String APP_KEY = "appKey";

    /**
     * 签名  sign
     */
    private static final String SIGN = "sign";


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 非控制器请求直接跳出
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        String contentType = request.getContentType();
        SortedMap<String, String> result = new TreeMap<>();
        if(StringUtil.isNotEmpty(contentType) && contentType.contains(MediaType.APPLICATION_JSON_VALUE)){
            HttpUtils.getBodyParam(request,result);
        }else {
            HttpUtils.getFormParams(request,result);
        }
        // 校验时间戳
        String timestamp = result.get(TIMESTAMP);
        String appKey = result.get(APP_KEY);
        String sign = result.get(SIGN);
        if(StringUtil.isEmpty(timestamp)){
            throw new BusinessException("参数错误 缺失[timeStamp]");
        }
        if(StringUtil.isEmpty(appKey)){
            throw new BusinessException("参数错误 缺失[appKey]");
        }
        if(StringUtil.isEmpty(sign)){
            throw new BusinessException("参数错误 缺失[sign]");
        }
       
        boolean validateTimeStamp = validateTimeStamp(Long.valueOf(timestamp));
        if(!validateTimeStamp){
            throw new BusinessException("当前请求参数已过期,不允许访问");
        }
        //校验签名
        boolean verifySign = SignUtils.verifySign(result,appSecret);

        if(!verifySign){
            log.info("签名 app_secret:{}",appSecret);
            throw new BusinessException("签名不正确,不允许访问");
        }
        return true;
    }


    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
       
    }
    /**
     * @description: 判断客户端的请求是否超过3分钟

     */
    private boolean validateTimeStamp(long timestamp) {
        Long tims = (System.currentTimeMillis()-timestamp) / (1000 * 60);
        //验证时间戳是否超过3分钟
        if (Math.abs(tims) > 3) {
            return false;
        } else {
            return true;
        }
    }
}

获取参数方法

public class HttpUtils {

    /**
     *
     * @param request
     * @return
     * @throws IOException
     */
    public static SortedMap<String, String> getAllParams(HttpServletRequest request) throws IOException {
        SortedMap<String, String> result = new TreeMap<>();
        // 获取URL上的参数
        getUrlParams(request, result);
        // 获取body参数
        getBodyParam(request, result);
        return result;
    }

    /**
     * 获取 Body 参数
     *
     */
    public static void getBodyParam(final HttpServletRequest request, SortedMap<String, String> result)
            throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
        String str = "";
        StringBuilder wholeStr = new StringBuilder();
        // 一行一行的读取body体里面的内容;
        while ((str = reader.readLine()) != null) {
            wholeStr.append(str);
        }
        wholeStr.trimToSize();
        String s = wholeStr.toString();
        if (!StringUtil.isEmpty(s)) {
            // 转化成json对象
            Map<String, String> allRequestParam = JSONObject.parseObject(s, Map.class);
            // 将URL的参数和body参数进行合并
            for (Map.Entry entry : allRequestParam.entrySet()) {
                result.put((String)entry.getKey(), (String)entry.getValue());
            }
        }
    }

    /**
     * 获取url参数
     */
    public static void getUrlParams(HttpServletRequest request, SortedMap<String, String> result) {
        String param = "";
        try {
            String urlParam = request.getQueryString();
            if (urlParam != null) {
                param = URLDecoder.decode(urlParam, "utf-8");
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String[] params = param.split("&");
        for (String s : params) {
            int index = s.indexOf("=");
            if (index != -1) {
                result.put(s.substring(0, index), s.substring(index + 1));
            }
        }
    }

    /**
     * 获取表单数据
     */
    public static void getFormParams(HttpServletRequest request, SortedMap<String, String> result) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String,String[]> entry:parameterMap.entrySet()){
            result.put(entry.getKey(), Arrays.stream(entry.getValue()).collect(Collectors.joining(",")));
        }
    }
}

签名工具

算法 参考微信 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

@Slf4j
public class SignUtils {

    /**
     * @param params
     *  所有的请求参数都会在这里进行排序加密
     * @return 验证签名结果
     */
    public static boolean verifySign(SortedMap<String, String> params,String key) {
        String urlSign = params.get("sign");
        log.info("Url Sign : {}", urlSign);
        if (StringUtil.isEmpty(urlSign)) {
            return false;
        }
        // 把参数加密
        String paramsSign = getParamsSign(params,key);
        log.info("Param Sign : {}", paramsSign);
        return !StringUtil.isEmpty(paramsSign) && urlSign.equals(paramsSign);
    }

    /**
     * @param params
     *
     * @return 得到签名
     */
    public static String getParamsSign(SortedMap<String, String> params,String key) {
        // 要先去掉 Url 里的 Sign
        params.remove("sign");
        StringBuffer sb = new StringBuffer();
        params.forEach((k,v)->{
            sb.append(k).append("=").append(v).append("&");
        });
        sb.append("key").append("=").append(key);

        try {
            String sign = new String(sb.toString().getBytes(), "utf-8");
            return SecureUtil.md5(sign).toUpperCase();
        } catch (UnsupportedEncodingException e) {
            throw new BusinessException("签名错误不支持utf-8编码");
        }

    }


}

彩蛋 也可以在网关 做,附上网关 获取参数 的类。

定义 filter 存到上下文

package com.xiaominfo.swagger.service.doc.config.test02;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;



/**
 * @Author 
 * @Date 2022/6/20 11:56
 */
@Component
@Slf4j
public class RequestCoverFilter implements GlobalFilter, Ordered {

    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    /**
     * ReadFormData
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain,
                                    GatewayContext gatewayContext) {
        final ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        return exchange.getFormData().doOnNext(multiValueMap -> {
            gatewayContext.setFormData(multiValueMap);
            log.debug("[GatewayContext]Read FormData:{}", multiValueMap);
        }).then(Mono.defer(() -> {
            Charset charset = headers.getContentType().getCharset();
            charset = charset == null ? StandardCharsets.UTF_8 : charset;
            String charsetName = charset.name();
            MultiValueMap<String, String> formData = gatewayContext.getFormData();
            /**
             * formData is empty just return
             */
            if (null == formData || formData.isEmpty()) {
                return chain.filter(exchange);
            }
            StringBuilder formDataBodyBuilder = new StringBuilder();
            String entryKey;
            List<String> entryValue;
            try {
                /**
                 * repackage form data
                 */
                for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
                    entryKey = entry.getKey();
                    entryValue = entry.getValue();
                    if (entryValue.size() > 1) {
                        for (String value : entryValue) {
                            formDataBodyBuilder.append(entryKey).append("=")
                                    .append(URLEncoder.encode(value, charsetName)).append("&");
                        }
                    } else {
                        formDataBodyBuilder.append(entryKey).append("=")
                                .append(URLEncoder.encode(entryValue.get(0), charsetName)).append("&");
                    }
                }
            } catch (UnsupportedEncodingException e) {
                // ignore URLEncode Exception
            }
            /**
             * substring with the last char '&'
             */
            String formDataBodyString = "";
            if (formDataBodyBuilder.length() > 0) {
                formDataBodyString = formDataBodyBuilder.substring(0, formDataBodyBuilder.length() - 1);
            }
            /**
             * get data bytes
             */
            byte[] bodyBytes = formDataBodyString.getBytes(charset);
            int contentLength = bodyBytes.length;
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {
                /**
                 * change content-length
                 *
                 * @return
                 */
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }

                /**
                 * read bytes to Flux<Databuffer>
                 *
                 * @return
                 */
                @Override
                public Flux<DataBuffer> getBody() {
                    return DataBufferUtils.read(new ByteArrayResource(bodyBytes),
                            new NettyDataBufferFactory(ByteBufAllocator.DEFAULT), contentLength);
                }
            };
            ServerWebExchange mutateExchange = exchange.mutate().request(decorator).build();
            log.info("[GatewayContext]Rewrite Form Data :{}", formDataBodyString);

            return chain.filter(mutateExchange);
        }));
    }

    /**
     * ReadJsonBody
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
        /**
         * join the body
         */
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            /*
             * read the body Flux<DataBuffer>, and release the buffer
             * //TODO when SpringCloudGateway Version Release To G.SR2,this can be update with the new version's feature
             * see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095
             */
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.retain(buffer);
                return Mono.just(buffer);
            });
            /**
             * repackage ServerHttpRequest
             */
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }
            };
            /**
             * mutate exchage with new ServerHttpRequest
             */
            ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
            /**
             * read body string with default messageReaders
             */
            return ServerRequest.create(mutatedExchange, messageReaders).bodyToMono(String.class)
                    .doOnNext(objectValue -> {
                        gatewayContext.setCacheBody(objectValue);
                        log.debug("[GatewayContext]Read JsonBody:{}", objectValue);
                    }).then(chain.filter(mutatedExchange));
        });
    }

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

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        /**
         * save request path and serviceId into gateway context
         */
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        GatewayContext gatewayContext = new GatewayContext();
        String path = request.getPath().pathWithinApplication().value();
        gatewayContext.setPath(path);
        gatewayContext.getFormData().addAll(request.getQueryParams());
        gatewayContext.setIpAddress(String.valueOf(request.getRemoteAddress()));
        HttpHeaders headers = request.getHeaders();
        gatewayContext.setHeaders(headers);
        log.debug("HttpMethod:{},Url:{}", request.getMethod(), request.getURI().getRawPath());

        /// 注意,因为webflux的响应式编程 不能再采取原先的编码方式 即应该先将gatewayContext放入exchange中,否则其他地方可能取不到
        /**
         * save gateway context into exchange
         */
        exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);

        // 处理参数
        MediaType contentType = headers.getContentType();
        long contentLength = headers.getContentLength();
        if (contentLength > 0) {
            if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                return readBody(exchange, chain, gatewayContext);
            }
            if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
                return readFormData(exchange, chain, gatewayContext);
            }
        }
        // TODO 多版本划区域控制后期实现

        log.debug("[GatewayContext]ContentType:{},Gateway context is set with {}", contentType, gatewayContext);
        return chain.filter(exchange);
    }

}

package com.xiaominfo.swagger.service.doc.config.test02;

import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
 * @Author 
 * @Date 2022/6/20 11:55
 */
@Data
public class GatewayContext {
    public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";

    /**
     * cache headers
     */
    private HttpHeaders headers;

    /**
     * baseHeader
     */
    //private BaseHeader baseHeader;

    /**
     * cache json body
     */
    private String cacheBody;
    /**
     * cache formdata
     */
    private MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();

    /**
     * ipAddress
     */
    private String  ipAddress;

    /**
     * path
     */
    private String path;

}


// 其他 filter 从上下文获取
GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);

elk

总结:在测试行业摸爬滚打也有十几年了,在学习的途中也是收藏了很多的学习资源,下面是软件测试到测试开发全职业生涯全套学习资料
【需要的可以点击下方官方推广小卡片扫码备注000免费领取】
 

在这里插入图片描述

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值