使用SpringBoot开发后台API接口时,接口存在哪些不安全因素?你一般是怎么解决的?本文主要介绍API接口的不安全因素以及保证接口安全的常用方法,重点介绍如何对接口进行签名。@pdaiSpringBoot接口-API接口的不安全因素有哪些?如何签署接口?准备知识点API接口有哪些不安全因素?保证接口安全的常用方法?AccessKey&SecretKey认证授权HTTPS接口签名(加密)实现案例定义注解AOP拦截请求封装实现接口接口测试实例源码更多内容准备知识点建议从接口整体安全体系的角度来理解,比如什么存在不安全因素、加密解密等知识点。API接口的不安全因素有哪些?从系统的角度来看,这里有一些不安全的因素:访问开放接口的开发者是否是合法的开发者?多客户端访问是合法客户端吗?访问接口的用户是否为合法用户?您是否有权访问该界面?接口传输http明文传输数据?其他方面,接口replay,上面介绍的幂等接口timeout,加timestamp控制?...保护接口的常用方法?针对以上接口中存在的不安全因素,这里介绍几种典型的接口安全保障方式。AccessKey&SecretKey的设计一般用在开发接口的安全上,保证是合法的开发者。AccessKey:开发者的唯一标识。SecretKey:开发者的密钥。以阿里云相关产品为例。从两个角度来看认证和授权。;第二:认证包括客户端的认证和用户的认证;客户端的认证一般是AppKey&AppSecret,或者ClientId&ClientSecret等,比如oauth2协议的客户端凭证模式https://api.xxxx.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRETgrant_type参数等于client_credentials指示客户端凭据方法,client_id是客户端ID,client_secret是客户端密码。返回token后,通过token访问其他接口。用于用户认证授权,比如oauth2协议的授权码方式(authorizationcode)和密码方式(resourceownerpasswordcredentials)https://api.xxxx.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID&scope=readgrant_type参数等于password表示密码方式,client_id为客户端id,username为用户名,password为密码。(PS:password模式只有在授权码模式(authorizationcode)不可用时才会使用,这里只是举例)可选参数scope表示应用的范围。(相关开发框架可以参考springsecurity、ApacheShiro、SA-Token等)从接口传输安全的角度来看,https是防止接口数据以明文形式传输的。具体可以看这里,HTTP存在以下安全问题:使用明文进行通信,内容可能被窃听;无法核实通讯方身份的,可以伪装通讯方身份;无法证明消息的完整性,消息可能被篡改。HTTPs并不是一个新的协议,而是让HTTP先和SSL(SecureSocketsLayer)通信,然后SSL和TCP再通信,也就是说HTTPs使用隧道进行通信。通过使用SSL,HTTPs具有加密(防窃听)、认证(防欺骗)和完整性保护(防篡改)等功能。接口签名(加密)接口签名(加密),主要是防止请求参数被篡改。尤其是对安全性要求比较高的接口,比如支付领域的接口。签名的主要流程首先,我们需要给客户端分配一个私钥,用于URL签名加密。一般的签名算法如下:1.首先将请求参数按key字母顺序排序,放入有序集合中(其他参数请参考后续补充部分);2、将排序后的数组键值对用&连接起来,组成参数串进行加密;3、在加密后的参数字符串前后加上私钥,然后用加密算法加密。获取标志,然后将其与请求接口一起传递给服务器。例如:https://api.xxxx.com/token?ke...服务端收到请求后,使用相同的算法获取服务端的签名,比较客户端的签名是否一致。如果一致,则请求有效;如果不一致,则返回指定的错误信息。补充:签什么?主要包括请求参数,这是最重要的部分。签名的目的是防止参数被篡改,需要对可能被篡改的参数进行签名;同时考虑到请求参数的来源可能在请求路径path中,在请求头中,在请求体中。如果AppKey&AppSecret分配给客户端,也可以加入签名计算;考虑到其他幂等性,token失效等,签名中也会加入涉及的参数,比如时间戳,序列号nonce等(这些参数可能来自header)补充:签名算法?一般都会涉及这部分内容,主要包括三点:密钥、签名算法、签名规则。它必须是对称加密算法。对称就是反着分析符号。这里使用相同的算法和规则计算符号,比较前端传来的符号是否一致。签名规则:如多重盐加密等;PS:可能有读者会问,我们可能会从一些客户端获取密钥、算法和规则(比如从前端SPA单页应用生成的js中获取密钥、算法和规则)规则),那么签名的意义在哪里??我认为签名是一种手段而不是目的。签名是增加攻击者攻击难度的一种手段,至少可以抵抗大部分简单的攻击,加上其他的防御手段(序列号、时间戳、token等)只是增加了攻击的难度。补充:签名和加密是一回事吗?严格来说不是一回事:签名是根据指定的算法和规则对参数进行符号计算,最后前后端使用相同的算法计算符号是否一致,以防止参数被篡改,所以可以看到参数都是纯文本的,只是加了一个计算符号。加密是对请求的参数进行加密,后端解密;同时,在某些情况下,返回的response也会被加密,由前端解密;这里有个加解密的过程,所以思路肯定是对称加密+时间戳接口时效等形式。补充:签名放在哪里?签名可以放在请求参数中(路径、正文等),更优雅的可以放在HEADER中,比如X-Sign(一般第三方的header参数都是以X-开头)补充:大厂是怎么做到的开放平台呢?可以从中学到什么?以腾讯开放平台为例,请参考腾讯开放平台第三方应用签名参数sig的说明。签署可能被篡改的参数)。@pdai定义注解包tech.pdai.springboot.api.sign.config.sign;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/***@authorpdai*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceSignature{}AOP拦截这里可以看到所有用户修改的参数点都需要根据规则修改Signpackagetech.pdai.springboot.api.sign.config.sign;importjava.io.IOException;importjava.nio.charset.StandardCharsets;importjava.util.Map;importjava.util.Objects;导入javax.servlet.http.HttpServletRequest;导入cn.hutool.core.text.CharSequenceUtil;导入org.aspectj.lang.annotation.Aspect;导入org.aspectj.lang.annotation.Before;导入org.aspectj.lang。annotation.Pointcut;导入org.springframework.stereotype.Component;导入org.springframework.util.CollectionUtils;导入org.springframework.web.context.request.RequestAttributes;导入org.springframework.web.context.request.RequestContextHolder;导入org.springframework.web.context.request.ServletRequestAttributes;导入org.springframework.web.context.request.ServletWebRequest;导入org.springframework.web.servlet.HandlerMapping;导入org.springframework.web.util.ContentCachingRequestWrapper;importtech.pdai.springboot.api.sign.config.exception.BusinessException;importtech.pdai.springboot.api.sign.util.SignUtil;/***@authorpdai*/@Aspect@ComponentpublicclassSignAspect{/***SIGN_HEADER。*/privatestaticfinalStringSIGN_HEADER="X-SIGN";/***切入点。*/@Pointcut("execution(@tech.pdai.springboot.api.sign.config.sign.Signature**(..))")privatevoidverifySignPointCut(){//nothing}/***验证标志。*/@Before("verifySignPointCut()")publicvoidverify(){HttpServletRequestrequest=((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();Stringsign=request.getHeader(SIGN_HEADER);//必须有登录头if(CharSequenceUtil.isBlank(sign)){thrownewBusinessException("nosignatureinheader:"+SIGN_HEADER);}//检查签名try{StringgeneratedSign=generatedSignature(request);if(!sign.equals(generatedSign)){thrownewBusinessException("无效签名");}}catch(Throwablethrowable){thrownewBusinessException("无效签名");}}privateStringgeneratedSignature(HttpServletRequestrequest)throwsIOException{//@RequestBodyStringbodyParam=null;if(requestinstanceofContentCachingRequestWrapper){bodyParam=newString(((ContentCachingRequestWrapper)request).getContentAsByteArray(),StandardCharsets.UTF_8);}//@RequestParamMap
