您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

如何保证API接口安全?

时间:2022-01-24 13:29:13  来源:  作者:DNF搬砖摸金达人

一、摘要

在实际的业务开发过程中,我们常常会碰到需要与第三方互联网公司进行技术对接,例如支付宝支付对接、微信支付对接、高德地图查询对接等等服务,如果你是一个创业型互联网,大部分可能都是对接别的公司api接口。

当你的公司体量上来了时候,这个时候可能有一些公司开始找你进行技术对接了,转变成由你来提供api接口,那这个时候,我们应该如何设计并保证API接口安全呢?

二、方案介绍

最常用的方案,主要有两种:

  • token方案
  • 接口签名

2.1、token方案

其中 token 方案,是一种在web端使用最广的接口鉴权方案,我记得在之前写过一篇《手把手教你,使用JWT实现单点登录》的文章,里面介绍的比较详细,有兴趣的朋友可以看一下,没了解的也没关系,我们在此简单的介绍一下 token 方案。

如何保证API接口安全?

 

从上图,我们可以很清晰的看到,token 方案的实现主要有以下几个步骤:

  • 1、用户登录成功之后,服务端会给用户生成一个唯一有效的凭证,这个有效值被称为token
  • 2、当用户每次请求其他的业务接口时,需要在请求头部带上token
  • 3、服务端接受到客户端业务接口请求时,会验证token的合法性,如果不合法会提示给客户端;如果合法,才会进入业务处理流程。

在实际使用过程中,当用户登录成功之后,生成的token存放在redis中时是有时效的,一般设置为2个小时,过了2个小时之后会自动失效,这个时候我们就需要重新登录,然后再次获取有效token。

token方案,是目前业务类型的项目当中使用最广的方案,而且实用性非常高,可以很有效的防止黑客们进行抓包、爬取数据。

但是 token 方案也有一些缺点!最明显的就是与第三方公司进行接口对接的时候,当你的接口请求量非常大,这个时候 token 突然失效了,会有大量的接口请求失败。

这个我深有体会,我记得在很早的时候,跟一家中、大型互联网公司进行联调的时候,他们提供给我的接口对接方案就是token方案,当时我司的流量高峰期时候,请求他们的接口大量报错,原因就是因为token失效了,当token失效时,我们会调用他们刷新token接口,刷新完成之后,在token失效与重新刷新token这个时间间隔期间,就会出现大量的请求失败的日志,因此在实际API对接过程中,我不推荐大家采用 token方案。

2.2、接口签名

接口签名,顾名思义,就是通过一些签名规则对参数进行签名,然后把签名的信息放入请求头部,服务端收到客户端请求之后,同样的只需要按照已定的规则生产对应的签名串与客户端的签名信息进行对比,如果一致,就进入业务处理流程;如果不通过,就提示签名验证失败。

如何保证API接口安全?

 

在接口签名方案中,主要有四个核心参数:

  • 1、Appid表示应用ID,其中与之匹配的还有appsecret,表示应用密钥,用于数据的签名加密,不同的对接项目分配不同的appid和appsecret,保证数据安全
  • 2、timestamp 表示时间戳,当请求的时间戳与服务器中的时间戳,差值在5分钟之内,属于有效请求,不在此范围内,属于无效请求
  • 3、nonce 表示临时流水号,用于防止重复提交验证
  • 4、signature 表示签名字段,用于判断接口请求是否有效。

其中签名的生成规则,分两个步骤:

  • 第一步:对请求参数进行一次md5加密签名
//步骤一
String 参数1 = 请求方式 + 请求URL相对地址 + 请求Body字符串;
String 参数1加密结果= md5(参数1)
  • 第二步:对第一步签名结果,再进行一次md5加密签名
//步骤二
String 参数2 = appsecret + timestamp + nonce + 参数1加密结果;
String 参数2加密结果= md5(参数2)

参数2加密结果,就是我们要的最终签名串。

接口签名方案,尤其是在接口请求量很大的情况下,依然很稳定。

换句话说,你可以将接口签名看作成对token方案的一种补充。

但是如果想把接口签名方案,推广到前后端对接,答案是:不适合。

因为签名计算非常复杂,其次,就是容易泄漏appsecret!

说了这么多,下面我们就一起来用程序实践一下吧!

二、程序实践

2.1、token方案

就像上文所说,token方案重点在于,当用户登录成功之后,我们只需要生成好对应的token,然后将其返回给前端,在下次请求业务接口的时候,需要把token带上。

具体的实践,也可以分两种:

  • 第一种:采用uuid生成token,然后将token存放在redis中,同时设置有效期2哥小时
  • 第二种:采用JWT工具来生成token,这种token是可以跨平台的,天然支持分布式,其实本质也是采用时间戳+密钥,来生成一个token。

下面,我们介绍的是第二种实现方式。

首先,编写一个jwt 工具。

public class JwtTokenUtil {
    //定义token返回头部
    public static final String AUTH_HEADER_KEY = "Authorization";
    //token前缀
    public static final String TOKEN_PREFIX = "Bearer ";
    //签名密钥
    public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";
    //有效期默认为 2hour
    public static final Long EXPIRATION_TIME = 1000L*60*60*2;
    /**
     * 创建TOKEN
     * @param content
     * @return
     */
    public static String createToken(String content){
        return TOKEN_PREFIX + JWT.create()
                .withSubject(content)
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .sign(Algorithm.Hmac512(KEY));
    }
    /**
     * 验证token
     * @param token
     */
    public static String verifyToken(String token) throws Exception {
        try {
            return JWT.require(Algorithm.HMAC512(KEY))
                    .build()
                    .verify(token.replace(TOKEN_PREFIX, ""))
                    .getSubject();
        } catch (TokenExpiredException e){
            throw new Exception("token已失效,请重新登录",e);
        } catch (JWTVerificationException e) {
            throw new Exception("token验证失败!",e);
        }
    }
}

接着,我们在登录的时候,生成一个token,然后返回给客户端。

@RequestMapping(value = "/login", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
    //...参数合法性验证
    //从数据库获取用户信息
    User dbUser = userService.selectByUserNo(userDto.getUserNo);
    //....用户、密码验证
    //创建token,并将token放在响应头
    UserToken userToken = new UserToken();
    BeanUtils.copyProperties(dbUser,userToken);
    String token = JwtTokenUtil.createToken(JSONObject.toJSONString(userToken));
    response.setHeader(JwtTokenUtil.AUTH_HEADER_KEY, token);
    //定义返回结果
    UserVo result = new UserVo();
    BeanUtils.copyProperties(dbUser,result);
    return result;
}

最后,编写一个统一拦截器,用于验证客户端传入的token是否有效。

@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从http请求头中取出token
        final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
        //如果不是映射到方法,直接通过
        if(!(handler instanceof HandlerMethod)){
            return true;
        }
        //如果是方法探测,直接通过
        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        //如果方法有JwtIgnore注解,直接通过
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method=handlerMethod.getMethod();
        if (method.isAnnotationPresent(JwtIgnore.class)) {
            JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);
            if(jwtIgnore.value()){
                return true;
            }
        }
        LocalAssert.isStringEmpty(token, "token为空,鉴权失败!");
        //验证,并获取token内部信息
        String userToken = JwtTokenUtil.verifyToken(token);
        //将token放入本地缓存
        WebContextUtil.setUserToken(userToken);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //方法结束后,移除缓存的token
        WebContextUtil.removeUserToken();
    }
}

在生成token的时候,我们可以将一些基本的用户信息,例如用户ID、用户姓名,存入token中,这样当token鉴权通过之后,我们只需要通过解析里面的信息,即可获取对应的用户ID,可以省下去数据库查询一些基本信息的操作。

同时,使用的过程中,尽量不要存放敏感信息,因为很容易被黑客解析!

2.2、接口签名

同样的思路,站在服务端验证的角度,我们可以先编写一个签名拦截器,验证客户端传入的参数是否合法,只要有一项不合法,就提示错误。

具体代码实践如下:

public class SignInterceptor implements HandlerInterceptor {

    @Autowired
    private AppSecretService appSecretService;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        //appId验证
        final String appId = request.getHeader("appid");
        if(StringUtils.isEmpty(appId)){
            throw new CommonException("appid不能为空");
        }
        String appSecret = appSecretService.getAppSecretByAppId(appId);
        if(StringUtils.isEmpty(appSecret)){
            throw new CommonException("appid不合法");
        }
        //时间戳验证
        final String timestamp = request.getHeader("timestamp");
        if(StringUtils.isEmpty(timestamp)){
            throw new CommonException("timestamp不能为空");
        }
        //大于5分钟,非法请求
        long diff = System.currentTimeMillis() - Long.parseLong(timestamp);
        if(Math.abs(diff) > 1000 * 60 * 5){
            throw new CommonException("timestamp已过期");
        }
        //临时流水号,防止重复提交
        final String nonce = request.getHeader("nonce");
        if(StringUtils.isEmpty(nonce)){
            throw new CommonException("nonce不能为空");
        }
        //验证签名
        final String signature = request.getHeader("signature");
        if(StringUtils.isEmpty(nonce)){
            throw new CommonException("signature不能为空");
        }
        final String method = request.getMethod();
        final String url = request.getRequestURI();
        final String body = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
        String signResult = SignUtil.getSignature(method, url, body, timestamp, nonce, appSecret);
        if(!signature.equals(signResult)){
            throw new CommonException("签名验证失败");
        }
        //检查是否重复请求
        String key = appId + "_" + timestamp + "_" + nonce;
        if(redisUtil.exist(key)){
            throw new CommonException("当前请求正在处理,请不要重复提交");
        }
        //设置5分钟
        redisUtil.save(key, signResult, 5*60);
        request.setAttribute("reidsKey",key);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        //请求处理完毕之后,移除缓存
        String value = request.getAttribute("reidsKey");
        if(!StringUtils.isEmpty(value)){
            redisUtil.remove(value);
        }
    }

}

签名工具类SignUtil:

public class SignUtil {

    /**
     * 签名计算
     * @param method
     * @param url
     * @param body
     * @param timestamp
     * @param nonce
     * @param appSecret
     * @return
     */
    public static String getSignature(String method, String url, String body, String timestamp, String nonce, String appSecret){
        //第一层签名
        String requestStr1 = method + url + body + appSecret;
        String signResult1 = DigestUtils.md5Hex(requestStr1);
        //第二层签名
        String requestStr2 = appSecret + timestamp + nonce + signResult1;
        String signResult2 = DigestUtils.md5Hex(requestStr2);
        return signResult2;
    }
}

签名计算,可以换成hamc方式进行计算,思路大致一样。

三、小结

上面介绍的token和接口签名方案,对外都可以对提供的接口起到保护作用,防止别人篡改请求,或者模拟请求。

但是缺少对数据自身的安全保护,即请求的参数和返回的数据都是有可能被别人拦截获取的,而这些数据又是明文的,所以只要被拦截,就能获得相应的业务数据。

对于这种情况,推荐大家对请求参数和返回参数进行加密处理,例如RSA、AES等加密工具。

同时,在生产环境,采用https方式进行传输,可以起到很好的安全保护作用!



Tags:API接口   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一、摘要在实际的业务开发过程中,我们常常会碰到需要与第三方互联网公司进行技术对接,例如支付宝支付对接、微信支付对接、高德地图查询对接等等服务,如果你是一个创业型互联网...【详细内容】
2022-01-24  Tags: API接口  点击:(5)  评论:(0)  加入收藏
前言前后端分离开发模式中,api文档是最好的沟通方式。今天就来说一说如何整合Swagger生成一套漂亮、美观、实用的接口文档。 源码传送门: https://gitee.com/huoqstudy/xiliu-...【详细内容】
2021-09-08  Tags: API接口  点击:(73)  评论:(0)  加入收藏
注:商业级功能效果演示,非开源,无源码。研发基础在之前AJAX请求数据加密效果之上,更进一步,对返回数据加密。之前是单纯用于登录场景。更广泛的场景是所有此类AJAX WEB API接口。...【详细内容】
2021-09-03  Tags: API接口  点击:(84)  评论:(0)  加入收藏
在实际的业务中,难免会跟第三方系统进行数据的交互与传递,那么如何保证数据在传输过程中的安全呢(防窃取)?除了https的协议之外,能不能加上通用的一套算法以及规范来保证传输的安...【详细内容】
2021-06-10  Tags: API接口  点击:(144)  评论:(0)  加入收藏
前几天我发文就已经给各位朋友提醒过了,你们的api早就被盗了。只是没有到那些人收割的时候而已。这不我在我们公司的官方群里就看见用户分享他朋友用其他的一些量化交易机...【详细内容】
2021-06-08  Tags: API接口  点击:(139)  评论:(0)  加入收藏
今天介绍我正在用的一款高效敏捷开发工具magic-api,顺便分享一点工作中使用它的心得缘起先说一下我为什么会使用这个工具?最近新启动一个项目,业务并不算复杂,那种典型的管理系...【详细内容】
2021-06-01  Tags: API接口  点击:(203)  评论:(0)  加入收藏
一、axios的封装在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应...【详细内容】
2020-10-12  Tags: API接口  点击:(276)  评论:(0)  加入收藏
API译者:DevOps亮哥如今,API已在软件、Web和移动应用程序开发领域无处不在,从企业内部到面向公众的应用以及与合作伙伴进行系统集成。通过使用API,开发人员可以创建满足各种客户...【详细内容】
2020-08-01  Tags: API接口  点击:(116)  评论:(0)  加入收藏
在6月中旬时,拼多多为了保障数据安全和运营信息安全,依据《拼多多开放平台开发者协议》等相关协议和规则,发布了对商家在使用API接口的时候开始收费的公告。 所谓API,百度百科...【详细内容】
2020-07-09  Tags: API接口  点击:(899)  评论:(0)  加入收藏
有没有遇到这样子的接口,放到互联网上面去,谁都可以调用,谁都可以访问,完全就是公开的,这样子的接口,如果只是普通的数据,其实可以考虑,只是可以考虑,但是,一般情况下,我们是不允许这样...【详细内容】
2020-06-20  Tags: API接口  点击:(89)  评论:(0)  加入收藏
▌简易百科推荐
while(1) 和 for(;;)它们不都是无限循环吗,作用应该一样啊,它们到底有什么区别?要回答这个问题,其实你各自编写一段while(1) 和 for(;;)的代码,编译对比一下代码大小和汇编文件,你...【详细内容】
2022-01-25  IT三宝    Tags:汇编   点击:(3)  评论:(0)  加入收藏
一、摘要在实际的业务开发过程中,我们常常会碰到需要与第三方互联网公司进行技术对接,例如支付宝支付对接、微信支付对接、高德地图查询对接等等服务,如果你是一个创业型互联网...【详细内容】
2022-01-24  DNF搬砖摸金达人    Tags:API接口   点击:(5)  评论:(0)  加入收藏
Swiss Army knife 可以说是本周的关键词了,多个项目采用该词来描述它的特性:像是能全方位解决浏览器“网络”操作的 CyberChef 方便你进行数据加密、解编码,还有帮你处理 JSON...【详细内容】
2022-01-24  HelloGitHub    Tags:GitHub   点击:(4)  评论:(0)  加入收藏
REST API 是当今可用的最常见的 Web 服务类型之一。它们允许包括浏览器应用程序在内的各种客户端通过 REST API 与服务器通信。因此,正确设计 REST API 非常重要,这样我们以后...【详细内容】
2022-01-13  杨同学编程    Tags:API   点击:(16)  评论:(0)  加入收藏
CPU对我们来说既熟悉又陌生,熟悉的是我们知道代码是被CPU执行的,当我们的线上服务出现问题时可能首先会查看CPU负载情况。陌生的是我们并不知道CPU是如何执行代码的,它对我们的...【详细内容】
2022-01-06  小心程序猿QAQ    Tags:代码   点击:(12)  评论:(0)  加入收藏
前言小黑在开发中遇到个问题,我负责的模块需要调用某个三方服务接口查询信息,查询结果直接影响后续业务逻辑的处理;这个接口偶尔会因网络问题出现超时,导致我的业务逻辑无法继续...【详细内容】
2022-01-04  小黑说Java    Tags:接口重试   点击:(25)  评论:(0)  加入收藏
我在会议演讲后经常收到的一个问题很奇怪,不是关于我演讲的内容,而是关于我的Linux桌面环境。人们对这个漂亮的发行版更感兴趣,而不是我刚才做的精彩演示我不是在抱怨,我喜欢我...【详细内容】
2022-01-04  木偶跳舞    Tags:Linux 开发   点击:(24)  评论:(0)  加入收藏
写在前面有时候可能做项目组长,负责一个项目开发,但是工作是内网,也没有公司的版本库权限,那这个时候,我们怎么用处理版本控制,可以用集中式的版本库工具SVN,或者分布式的Git,这里和...【详细内容】
2021-12-31  山河已无恙被注册了    Tags:Git版本库   点击:(47)  评论:(0)  加入收藏
1、什么是YAMLYAML是"YAML Ain’t a Markup Language"(YAML不是一种标记语言)的递归缩写。YAML的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。主要强度...【详细内容】
2021-12-29  编程菌zfn    Tags:YAML   点击:(23)  评论:(0)  加入收藏
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(28)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条