三方开放接口,Springboot通过AOP实现API接口的签名验证

这篇具有很好参考价值的文章主要介绍了三方开放接口,Springboot通过AOP实现API接口的签名验证。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

对外开放的接口,需要验证请求方发送过来的数据确实是由发送方发起的,并且中途不能被篡改和伪造,所以才会对接口的访问进行签名验证,以保证双方获取到的原来的信息是没有经过篡改的。

实现方法

对请求的信息内容,通过MD5运算或者其他算法(必须是不可逆的签名算法)生成签名标识,后端拿到请求的信息内容,经过同样的算法得到签名标识,比对和接收到的签名一致,则验证为真

签名规则

这里我自己只用到了timestamp加入签名,实际开发种可能会用到appId、pwd等参数,按实际需求加入到规则中即可,签名规则根据实际变更,此处只作参考

1. 所有的参数按参数名升序排序
2. 按参数名及参数值互相连接组成一个请求参数串(paramStr),格式如下:
body内容#createName=default#createUser=10000#reportUser=张三#state=0#
注:参数有3种,body直接在后面拼接#,前2种按参数名及参数值互相连接,拼接顺序:body#queryParams#pathUrl#,每个参数后面以#结束
        ①URL占位符中的参数
        ②QueryParams参数,普通请求参数
        ③body参数
3. 将secretKey(服务器密钥:ee4xxxxxxxxxxxx3e)和timestamp,拼接到请求参数串的头部,得到签名字符串(signStr)
secretKey="+secretKey#timestamp="+timestamp+"#paramStr+"
4. 将signStr通过MD5加密,得到签名字符串(sign)
5. timestamp 、sign 放到 Headers中,与其他接口请求参数一起发送给服务端

步骤1:自定义签名验证注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME )
public @interface CheckSign {
}

步骤2:自定义验证签名切面AOP,实现签名验证

@Aspect   //定义一个切面
@Configuration
@Slf4j
public class CheckSignAspect {

    @Value("${sign.expireTime}")
    private long expireTime;//接口签名验证超时时间
    @Value("${sign.secretKey}")
    private String secretKey;//接口签名唯一密钥

    // 定义切点Pointcut
    @Pointcut("@annotation(com.jxzx.verify.common.annotation.CheckSign)")
    public void excudeService() {
    }

    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        log.info("开始验证签名");
        try {
            ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = Objects.requireNonNull(sra).getRequest();

            String timestamp = request.getHeader("timestamp");//获取timestamp参数
            String sign = request.getHeader("sign");//获取sign参数

            if (StrUtil.isBlank(timestamp) || StrUtil.isBlank(sign)) {
                return RestResult.failed("timestamp和sign参数不能为空");
            }
            long requestTime = Long.valueOf(timestamp);
            long now = System.currentTimeMillis() / 1000;
            log.info("now={}", now);
            // 请求发起时间与当前时间超过expireTime,则接口请求过期
            if (now - requestTime > expireTime) {
                return RestResult.failed("接口请求过期");
            }

            String generatedSign = generatedSignature(request, timestamp);
            if (!generatedSign.equals(sign)) {
                return RestResult.failed("签名校验错误");
            }

            Object result = joinPoint.proceed();
            return result;
        } catch (Throwable t) {
            return RestResult.failed("签名校验异常");
        }

    }
    
    //获取请求参数并生成签名
    private String generatedSignature(HttpServletRequest request, String timestamp) {
        //获取RequestBody参数,此处需要配合过滤器处理request后才能获取
        String bodyParam = null;
        if (request instanceof ContentCachingRequestWrapper) {
            bodyParam = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), StandardCharsets.UTF_8);
        }

        //获取RequestParam参数
        Map<String, String[]> requestParameterMap = request.getParameterMap();

        //获取PathVariable参数
        ServletWebRequest webRequest = new ServletWebRequest(request, null);
        Map<String, String> requestPathMap = (Map<String, String>) webRequest.getAttribute(
                HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

        return SignUtil.sign(bodyParam, requestParameterMap, requestPathMap, secretKey, timestamp);
    }

}

注意:

获取RequestBody参数,@RequestBody读取参数主要是通过request中的ServletInputStream传输,SpirngMvc通过@RequestBody读取流中的数据封装到对象中。因为stream只能被读取一次,如果这里我们通过request读取,后面的SpringMvc就读取不到了,因此通过Filter对request进行处理。

新增一个过滤器
public class RequestCachingFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
        boolean isFirstRequest = !isAsyncDispatch(request);
        HttpServletRequest requestWrapper = request;
        if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
            requestWrapper = new ContentCachingRequestWrapper(request);
        }
        try {
            filterChain.doFilter(requestWrapper, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
配置过滤器
@Configuration
public class FilterConfig {
    @Bean
    public RequestCachingFilter requestCachingFilter() {
        return new RequestCachingFilter();
    }

    @Bean
    public FilterRegistrationBean requestCachingFilterRegistration(
            RequestCachingFilter requestCachingFilter) {
        FilterRegistrationBean bean = new FilterRegistrationBean(requestCachingFilter);
        bean.setOrder(1);
        return bean;
    }
}
生成签名的工具类
public class SignUtil {

    /**
     * 使用 Map按key进行排序
     *
     * @param map
     * @return
     */
    public static Map<String, String> sortMapByKey(Map<String, String> map) {
        if (map == null || map.isEmpty()) {
            return null;
        }
        //升序排序
        Map<String, String> sortMap = new TreeMap<>(String::compareTo);
        sortMap.putAll(map);
        return sortMap;
    }

    public static String sign(String body, Map<String, String[]> params, Map<String, String> requestPathMap, String secretKey, String timestamp) {
        StringBuilder sb = new StringBuilder();
        if (CharSequenceUtil.isNotBlank(body)) {
            sb.append(body).append('#');
        }

        if (!CollectionUtils.isEmpty(params)) {
            params.entrySet()
                    .stream()
                    .sorted(Map.Entry.comparingByKey())
                    .forEach(paramEntry -> {
                        String paramValue = String.join(",", Arrays.stream(paramEntry.getValue()).sorted().toArray(String[]::new));
                        sb.append(paramEntry.getKey()).append("=").append(paramValue).append('#');
                    });
        }

        if (ArrayUtil.isNotEmpty(requestPathMap)) {
            for (String key : requestPathMap.keySet()) {
                String value = requestPathMap.get(key);
                sb.append(key).append("=").append(value).append('#');
            }

        }


        return SecureUtil.md5(String.join("#", secretKey, timestamp, sb.toString()));
    }
}

步骤3:使用

在我们需要验证签名的controller上加上 @CheckSign 即可文章来源地址https://www.toymoban.com/news/detail-729797.html

    @ApiOperation(value = "验证签名")
    @PostMapping("/checkSign")
    @CheckSign
    public RestResult postTestPdf(
            @PathVariable("name") String name,
            @PathVariable("age") String age,
            @ApiParam(value = "搜索条件", required = true) String orderId,
            @ApiParam(value = "搜索条件1", required = true) String orderNo,
            @RequestBody(required = true) @Validated JSONArray jsonArray
    ) {

        return RestResult.success(jsonArray);
    }

到了这里,关于三方开放接口,Springboot通过AOP实现API接口的签名验证的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信开放平台申请移动应用时如何获取已经安装的第三方app的应用签名

    一.问题描述 当我们在微信开放平台申请移动应用时,进行到了如下步骤,需要获取应用签名,下面给出解决方案 二.问题解决 先来到微信提供的获取应用签名网页下载获取应用签名的app,网址如下: https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html 点击网址进入如

    2024年02月16日
    浏览(39)
  • springboot aop实现接口防重复操作

    一、前言 有时在项目开发中某些接口逻辑比较复杂,响应时间长,那么可能导致重复提交问题。 二、如何解决 1.先定义一个防重复提交的注解。 2.编写防重复操作的AOP 3.接下来定义redisService类 4.最后在Controller接口加上注解就行了。

    2024年02月11日
    浏览(36)
  • 如何设计安全可靠的开放接口---之签名(sign)

    1. 如何设计安全可靠的开放接口—之Token 2. 如何设计安全可靠的开放接口—之AppId、AppSecret 3. 如何设计安全可靠的开放接口—之签名(sign) 4. 如何设计安全可靠的开放接口【番外篇】—关于MD5应用的介绍 5. 如何设计安全可靠的开放接口—还有哪些安全保护措施 6. 如何设计安全

    2024年02月10日
    浏览(35)
  • 最详细的SpringBoot实现接口校验签名调用

    代码地址:GitHub - passerbyYSQ/DemoRepository: 各种开发小demo 概念 开放接口 验签 接口验签调用流程 1. 约定签名算法 2. 颁发非对称密钥对 3. 生成请求参数签名 4. 请求携带签名调用 代码设计 1. 签名配置类 2. 签名管理类 3. 自定义验签注解 4. AOP实现验签逻辑 5. 解决请求体只能读取一

    2024年02月09日
    浏览(25)
  • 【springboot】spring的Aop结合Redis实现对短信接口的限流

    场景: 为了限制短信验证码接口的访问次数,防止被刷,结合Aop和redis根据用户ip对用户限流 首先我们创建一个 Spring Boot 工程,引入 Web 和 Redis 依赖,同时考虑到接口限流一般是通过注解来标记,而注解是通过 AOP 来解析的,所以我们还需要加上 AOP 的依赖,最终的依赖如下:

    2024年02月05日
    浏览(37)
  • java通过httpclient携带请求头参数获取第三方文件流接口并实现实现文件下载

    创建httpclient 请求,并在header携带指定key,也可根据实际需要携带token等信息。获取第三方接口返回的文件输入流并写到本地response中,实现返回文件流,前端通过js的a标签进行下载。 代码如下: 前端js代码

    2024年02月16日
    浏览(35)
  • 超详细!完整版!基于spring对外开放接口的签名认证方案(拦截器方式)

    由于项目需要开发第三方接口给多个供应商,为保证Api接口的安全性,遂采用Api接口签名验证。 请求发起时间得在限制范围内 请求的用户是否真实存在 是否存在重复请求 请求参数是否被篡改 1、服务端生成一对 accessKey/secretKey密钥对,将 accessKey公开给客户端,将 secretKey 保

    2024年01月22日
    浏览(35)
  • SpringBoot调用第三方WebService接口的两种实现方式

    WebService接口的发布通常一般都是使用WSDL(web service descriptive language)文件的样式来发布的,该文档包含了请求的参数信息,返回的结果信息,我们需要根据WSDL文档的信息来编写相关的代码进行调用WebService接口。接下来我将采用常见的两种方式调用WebService接口。 目前我需要

    2024年02月12日
    浏览(49)
  • SpringBoot自定义注解+AOP+redis实现防接口幂等性重复提交,从概念到实战

    本文为千锋教育技术团独家创作,更多技术类知识干货,点个关注持续追更~ 接口幂等性是Web开发中非常重要的一个概念,它可以保证多次调用同一个接口不会对结果产生影响。如果你想了解更多关于接口幂等性的知识,那么本文就是一个不错的起点。 在Web开发中,我们经常

    2024年02月03日
    浏览(42)
  • Springboot实现上传文件,并实现调用第三方接口post请求多文件上传文件

    项目过程中,经常会有和第三方接口打交道的过程,今天实现调用第三方上传文件的接口!! 通常拿到第三方的接口文档的时候,不是第一时间先写代码,而是详细阅读接口文档。若接口需要第三方提供的基本参数,例如signkey, secrect等,也可以是其他的,查看文档里是否提

    2024年02月16日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包