对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)

这篇具有很好参考价值的文章主要介绍了对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

一个web系统,从接口的使用范围也可以分为对内和对外两种,对内的接口主要限于一些我们内部系统的调用,多是通过内网进行调用,往往不用考虑太复杂的鉴权操作。但是,对于对外的接口,我们就不得不重视这个问题,外部接口没有做鉴权的操作就直接发布到互联网,而这不仅有暴露数据的风险,同时还有数据被篡改的风险,严重的甚至是影响到系统的正常运转
方案一:Spring Boot+Aop+注解实现Api接口签名验证
方案二:在已有接口上,拦截器拦截,接口路径,白名单匹配

Spring Boot+Aop+注解实现Api接口签名验证

appId和secret+token+时间戳
token的生成过程中在加入时间戳,校验token正确性之前先校验时间戳是否在一定时间窗口内(比如说1分钟),如果超过一分钟,直接拒绝请求,通过后再校验token。
新接口加上注解
由于项目需要开发第三方接口给多个供应商,为保证Api接口的安全性,遂采用Api接口签名验证。
一、为什么需要 API 接口签名
对外开放的 API 接口都会面临一些安全问题,例如伪装攻击、篡改攻击、重放攻击以及数据信息泄漏的风险。利用 API 接口签名能方便的防范这些安全问题和风险。在设计 API 接口签名时主要考虑以下几点:
请求发起时间得在限制范围内
请求的用户是否真实存在
是否存在重复请求
请求参数是否被篡改
1.保证请求数据正确
当请求中的某一个字段的值变化时,原有的签名结果就会发生变化。所以,只要参数发生变化,签名就要发生变化,否则请求将会是一个无效的请求。
2.保证请求来源合法
一般情况下,生成签名的算法都会成对出现一个 appKey 和一个 appSecret,根据 appKey 能识别出调用者身份;根据 appSecret 能识别出签名是否合法。
3.识别接口的时效性
一般情况下,签名和参数中会包含时间戳,这样服务端就可以验证客户端请求是否在有效时间内,从而避免接口被长时间的重复调用
4.是否存在重复请求

API 接口签名验签实现机制

签名验签流程图
第三方接口鉴权设计,安全,解决方案,spring boot,java

1 客户端向服务端申请 appKey,appSecret ,服务端下发 appKey,appSecret。
2 客户端集成 SDK 产生 sign,将 appKey,请求参数,时间戳,sign,随机数nonce 发送到服务端,服务端根据请求参数使用 SDK 中的签名规则生成签名来验证sign的合法性,之后返回结果。

实现思路:

我们按照主要防御措施先后顺序来实现,首先已知我们得到以下四个参数:

// 供应商的id,验证用户的真实性
String appid = request.getHeader("appid");
// 请求发起的时间
String timestamp = request.getHeader("timestamp");
// 随机数
String nonce = request.getHeader("nonce");
// 签名算法生成的签名
String sign = request.getHeader("sign");
 1.请求发起时间得在限制范围内  
像这种比较简单,就是获取服务器的当前时间去跟请求发起时间比较。
// 限制为(含)60秒以内发送的请求
long time = 60;
long now = System.currentTimeMillis() / 1000;
if (now - Long.valueOf(timestamp) > time) {
    return ObjectResponse.fail("请求发起时间超过服务器限制时间");
}

2.请求的用户是否真实存在
一般会有以下两个场景
场景一:在前后端分离的模式中,用户登录后得到token,用户调用接口时传递token来确保用户的真实性。
场景二:接口调用方不需要登录,那么我们接口提供方可以提供appid(调用时需要传递)与secret(在签名算法中使用)给接口调用方来验证用户的真实性。
这里我主要说一下场景二,如下:
// 查询appid是否正确来验证用户的真实性文章来源地址https://www.toymoban.com/news/detail-861831.html

CoreApiKey apiKey = coreApiKeyService.selectByAppid(appid);
if (apiKey == null) {
    return ObjectResponse.fail("appid参数错误");
}
  1. 是否存在重复请求
    这里利用nonce参数,每次请求时先判断nonce在redis是否存在,存在则认为是重复请求,不存在就存放到redis中。但是这会有一个问题,随着请求的 次数越来越多,那么redis存放的nonce集合会越来越大,这肯定不是我们所期望的。这时我们可以巧妙的利用在请求发起时间得在限制范围内中的time(服务器限制60秒以内发生的请求),因为此步骤主要是验证请求是否重复,如果timestamp时间戳变了,那就不是重复请求了,所以我们可以在nonce存放到redis时给它设置一个过期时间(60秒),这样既保证了nonce的唯一性也不会发生nonce集合的无限大。
// 验证请求是否重复
if (redisService.hasKeyHashItem("third_party_key", apiKey.getAppid() + nonce)) {
    return ObjectResponse.fail("请不要发送重复的请求");
} else {
    // 如果nonce没有存在缓存中,则加入,并设置失效时间(秒)
    redisService.setHashItem("third_party_key", apiKey.getAppid() + nonce, nonce, time);
}
  1. 请求参数是否被篡改
    利用签名算法来生成签名。主要就是接口调用方的签名算法必须与接口提供方的签名算法一致。签名算法可以自己捣鼓捣鼓,我这里是先对key进行字典序排序,然后以url的参数格式进行拼接(secret在最后拼接),最后进行md5加密,以下一个Api接口签名验证就大功告成啦!
JSONObject signObj = new JSONObject();
signObj.put("appid", appid);
signObj.put("timestamp", timestamp);
signObj.put("nonce", nonce);
String mySign = getSign(signObj, apiKey.getSecret());
// 验证签名
if (!mySign.equals(sign)) {
    return ObjectResponse.fail("签名信息错误");
}

/**
 * 获取签名信息
 * @param data
 * @param secret
 * @return
 */
private static String getSign(JSONObject data, String secret) {
    // 由于map是无序的,这里主要是对key进行排序(字典序)
    Set<String> keySet = data.keySet();
    String[] keyArr = keySet.toArray(new String[keySet.size()]);
    Arrays.sort(keyArr);
    StringBuilder sbd = new StringBuilder();
    for (String k : keyArr) {
        if (StringUtil.isNotEmpty(data.getString(k))) {
            sbd.append(k + "=" + data.getString(k) + "&");
        }
    }
    // secret最后拼接
    sbd.append("secret=").append(secret);
    return MD5Util.encode(sbd.toString());
}
 5.基于SringBoot以及Redis使用Aop来实现Api接口签名验证的源码  
@Component
@Aspect
@Slf4j
public class ThridPartyApiAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private RedisService redisService;

    @Autowired
    private CoreApiKeyService coreApiKeyService;

    /**
     * 表示匹配带有自定义注解的方法
     */
    @Pointcut("@annotation(com.stan.framework.anno.ThridPartyApi)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) {
        try {
            // 供应商的id,验证用户的真实性
            String appid = request.getHeader("appid");
            // 请求发起的时间
            String timestamp = request.getHeader("timestamp");
            // 随机数
            String nonce = request.getHeader("nonce");
            // 签名算法生成的签名
            String sign = request.getHeader("sign");
            if (StringUtil.isEmpty(appid) || StringUtil.isEmpty(timestamp) || StringUtil.isEmpty(nonce) || StringUtil.isEmpty(sign)) {
                return ObjectResponse.fail("请求头参数不能为空");
            }
            // 限制为(含)60秒以内发送的请求
            long time = 60;
            long now = System.currentTimeMillis() / 1000;
            if (now - Long.valueOf(timestamp) > time) {
                return ObjectResponse.fail("请求发起时间超过服务器限制时间");
            }
            // 查询appid是否正确
            CoreApiKey apiKey = coreApiKeyService.selectByAppid(appid);
            if (apiKey == null) {
                return ObjectResponse.fail("appid参数错误");
            }
            // 验证请求是否重复
            if (redisService.hasKeyHashItem("third_party_key", apiKey.getAppid() + nonce)) {
                return ObjectResponse.fail("请不要发送重复的请求");
            } else {
                // 如果nonce没有存在缓存中,则加入,并设置失效时间(秒)
                redisService.setHashItem("third_party_key", apiKey.getAppid() + nonce, nonce, time);
            }
            JSONObject signObj = new JSONObject();
            signObj.put("appid", appid);
            signObj.put("timestamp", timestamp);
            signObj.put("nonce", nonce);
            String mySign = getSign(signObj, apiKey.getSecret());
            // 验证签名
            if (!mySign.equals(sign)) {
                return ObjectResponse.fail("签名信息错误");
            }
            try {
                return point.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return ObjectResponse.fail("解析请求参数异常");
        }
        return null;
    }

    /**
     * 获取签名信息
     * @param data
     * @param secret
     * @return
     */
    private static String getSign(JSONObject data, String secret) {
        // 由于map是无序的,这里主要是对key进行排序(字典序)
        Set<String> keySet = data.keySet();
        String[] keyArr = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArr);
        StringBuilder sbd = new StringBuilder();
        for (String k : keyArr) {
            if (StringUtil.isNotEmpty(data.getString(k))) {
                sbd.append(k + "=" + data.getString(k) + "&");
            }
        }
        // secret最后拼接
        sbd.append("secret=").append(secret);
        return MD5Util.encode(sbd.toString());
    }
}

6. 测试签名
/**
 * @Author lc
 * @description:
 * @Date 2022/4/26 15:19
 * @Version 1.0
 */

@RestController
@Api(tags = "对外接口")
@RequestMapping("/test")
public class ThirdPartyApiAspectController  {

    @ThirdPartyApi
    @ApiOperation("对接接口测试")
    @PostMapping(value = "/test")
    public ResponseVO<String> test(HttpServletRequest request){

        return new ResponseVO ("签名校验");
    }
}

到了这里,关于对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • smiley-http-proxy-servlet 实现springboot 接口反向代理,站点代理,项目鉴权,安全的引入第三方项目服务

    2023-11-15 SmileSayBoot 开源,项目内扩展 实现了动态代理,可代理API接口/站点,若对你有用,请点个☆star 谢谢,能力有限,不喜勿喷。 背景: 项目初期 和硬件集成,实现了些功能服务,由于是局域网环境,安全问题当时都可以最小化无视。随着对接的服务越来越多,部分功能

    2024年02月12日
    浏览(53)
  • Spring Boot整合OAuth2实现GitHub第三方登录

    Github OAuth 第三方登录示例 第三方登录的原理是借助OAuth授权来实现,首先用户先向客户端提供第三方网站的数据证明自己的身份获取授权码,然后客户端拿着授权码与授权服务器建立连接获得一个Access Token,之后客户端就可以通过Access Token来与资源服务器进行交互。 使用O

    2024年02月08日
    浏览(67)
  • 如何在Spring Boot中优雅地重试调用第三方API?

    🎉如何在Spring Boot中优雅地重试调用第三方API? ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:架构设计 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习 🍹文章作者技术和水平有限,如果文中出现错误,

    2024年02月05日
    浏览(104)
  • uniapp 对接谷歌第三方登录

    1.登录谷歌开发者后台 https://console.developers.google.com/ 2.添加凭证 3.拿到客户端id后,项目中配置google登录:  示例代码:

    2024年04月29日
    浏览(47)
  • 天猫精灵家居对接第三方设备(详细版)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档· 文章目录 前言 补充准备阶段(内网映射到外网) 一、天猫精灵官网配置准备 二、天猫精灵部分实现代码 三、天猫精灵配置技能广场 四、天猫精灵语音调试测试 总结 随着物联网的发展,众多的硬件

    2024年02月06日
    浏览(43)
  • 微信第三方平台对接小程序发版

    微信第三方平台是商家把公众号或小程序代交给服务商来管理, 当商家把小程序授权给第三方平台后,同时授权小程序开发与数据分析,小程序后台就无法发版 ,必须开发人员调小程序的api才能实现发版;如不需要调第三方平台接口的方式发布,取消 小程序开发与数据分析

    2024年02月16日
    浏览(75)
  • Spring Boot 最新版3.x 集成 OAuth 2.0实现认证授权服务、第三方应用客户端以及资源服务

    Spring Boot 3 已经发布一段时间,网上关于 Spring Boot 3 的资料不是很多,本着对新技术的热情,学习和研究了大量 Spring Boot 3 新功能和新特性,感兴趣的同学可以参考 Spring 官方资料全面详细的新功能/新改进介绍 Spring 版本升级到6.x JDK版本至少17+ … 新特性有很多,本文主要针对

    2024年02月02日
    浏览(73)
  • spring boot整合第三方微信开发工具 weixin-java-miniapp 实现小程序微信登录

    有时候项目需要用到微信登录或获取用户的手机号码,weixin-java-miniapp是一个好用的第三方工具,不用我们自己写httpcline调用。 导入jar包 添加一个resource.properties文件,写上小程序的appid和secret 添加两个配置文件 WxMaProperties.java WxMaConfiguration.java 如何使用 小程序给微信发送消息

    2024年02月16日
    浏览(59)
  • 海康威视iSC 平台第三方对接门禁权限分享

    一、 场景描述 iSC 平台的门禁产品及功能在项目应用广泛,第三方对接门禁权限功能的需求也越来也 多,由于门禁权限下发需要涉及到往设备上下发,第三方接口调用一旦不正确,不合理的接 口调用很容易给我们平台造成额外的性能消耗,甚至导致我们平台挂掉,特此梳理

    2024年02月02日
    浏览(361)
  • 3PL第三方物流如何对接TSC EDI?

    TractorSupply(以下简称为TSC)是一家总部位于美国的零售公司,主要专注于农业、家庭和宠物用品。在美国拥有数百家门店,广泛分布在城市、城镇和农村地区,这使得他们能够满足各种地理位置和社区的需求。为了确保其供应链以及物流运营的高效性,TSC选择与3PL(第三方物

    2024年02月07日
    浏览(45)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包