企业API接口设计(token、timestamp、sign)之具体实现 本号主要用于分享企业中常用的技术,更加侧重于实用,欢迎

日期:2019-08-13 19:25:47 来源:互联网 编辑 : 小TT 阅读人数:205

一:token 简介Token:访问令牌access token, 用于接口中, 用于标识接口调用者的身份、凭证,减少用户名和密码的传输次数。一般情况下客户端(接口调用方)需要先向端申请一个接口调用的

企业API接口设计(token、timestamp、sign)之具体实现 本号主要用于分享企业中常用的技术,更加侧重于实用,欢迎关注、收藏,便于浏览其它更多实用的历史文章。 一:token 简介 二:timestamp 简介 三:sign 简介 四:防止重复提交 五:使用流程(图1)

一:token 简介

Token:访问令牌access token, 用于接口中, 用于标识接口调用者的身份、凭证,减少用户名和密码的传输次数。一般情况下客户端(接口调用方)需要先向端申请一个接口调用的,会给出一个appId和一个key, key用于参数签名使用,注意key保存到客户端,需要做一些安全处理,防止泄露。

Token的值一般是UUID,服务端生成Token后需要将token做为key,将一些和token关联的信息作为value保存到缓存中(redis)当一个请求过来后,就去缓存中查询这个Token是否存在,存在则调用接口,不存在返回接口错误,一般通过或者过滤器来实现,Token分为两种:

API Token(接口令牌) 用于访问不需要用户登录的接口,如登录、注册、一些基本数据的获取等。 获取接口令牌需要拿appId、timestamp和sign来换,sign=加密(timestamp+key)

USER Token(用户令牌) 用于访问需要用户登录之后的接口,如:获取我的基本信息、保存、修改、删除等操作。获取用户令牌需要拿用户名和密码来换

关于Token的时效性:token可以是一次性的、也可以在一段时间范围内是有效的,具体使用哪种看业务需要。

一般token、timestamp和sign 三个参数会在接口中会同时作为参数传递,每个参数都有各自的用途。

二:timestamp 简介

DoS

DoS是Denial of Service的简称,即拒绝服务,造成DoS的攻击行为被称为DoS攻击,其目的是使计算机或网络无法提供正常的服务。最常见的DoS攻击有计算机网络带宽攻击和连通性攻击。

DoS攻击是指故意的攻击网络协议实现的缺陷或直接通过野蛮手段残忍地耗尽被攻击对象的资源,目的是让目标计算机或网络无法提供正常的服务或资源访问,使目标服务停止响应甚至崩溃,而在此攻击中并不包括侵入目标或目标网络设备。这些服务资源包括网络带宽,文件空间容量,开放的进程或者允许的连接。这种攻击会导致资源的匮乏,无论计算机的处理速度多快、内存容量多大、网络带宽的速度多快都无法避免这种攻击带来的后果。

Pingflood: 该攻击在短时间内向目的主机发送大量ping包,造成网络堵塞或主机资源耗尽。

些队列,造成了资源的大量消耗而不能向正常请求提供服务。

Ping of Death:根据TCP/IP的规范,一个包的长度最大为65536字节。尽管一个包的长度不能超过65536字节,但是一个包分成的多个片段的叠加却能做到。当一个主机收到了长度大于65536字节的包时,就是受到了Ping of Death攻击,该攻击会造成主机的宕机。

PingSweep:使用ICMP Echo轮询多个主机。

三:sign 简介

nonce:随机值,是客户端随机生成的值,作为参数传递过来,随机值的目的是增加sign签名的多变性。随机值一般是数字和字母的组合,6位长度,随机值的组成和长度没有固定规则。

sign: 一般用于参数签名,防止参数被非法篡改,最常见的是修改金额等重要敏感参数, sign的值一般是将所有非空参数按照升续排序+token+key+timestamp+nonce(随机数)拼接在一起,使用某种加密算法进行加密,作为接口中的一个参数sign来传递,也可以将sign放到请求头中。接口在网络传输过程中如果被挟持,并修改其中的参数值,再继续调用接口,虽然参数的值被修改了,但是因为不知道sign是如何计算出来的,不知道sign都有哪些值构成,不知道以怎样的顺序拼接在一起的,最重要的是不知道签名字符串中的key是什么,所以可以篡改参数的值,但没法修改sign的值,当调用接口前会按照sign的规则重新计算出sign的值和接口传递的sign参数的值做比较,如果相等表示参数值没有被篡改,如果不等,表示参数被非法篡改了,就不执行接口了。

四:防止重复提交

对于一些重要的操作需要防止客户端重复提交的(如非幂等性重要操作)具体办法是当请求第一次提交时将sign作为key保存到redis,并设置超时时间,超时时间和Timestamp中设置的差值相同。当同一个请求第二次访问时会先检测redis是否存在该sign,如果存在则证明重复提交了,接口就不再继续调用了。如果sign在缓存中因过期时间到了,而被删除了,此时当这个url再次请求时,因token的过期时间和sign的过期时间一直,sign过期也意味着token过期,那样同样的url再访问会因token错误会被拦截掉,这就是为什么sign和token的过期时间要保持一致的原因。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)

对于哪些接口需要防止重复提交可以自定义个注解来标记。

注意:

所有的安全措施都用上的话有时候难免太过复杂,在实际项目中需要根据自身情况作出裁剪,比如可以只使用签名机制就可以保证信息不会被篡改,或者定向提供服务的时候只用Token机制就可以了。如何裁剪,全看项目实际情况和对接口安全性的要求。

五:使用流程

接口调用方客户端向接口提供方申请接口调用,申请成功后,接口提供方会给接口调用方一个appId和一个key参数

客户端携带参数appId、timestamp、sign去调用端的API token,其中sign=加密(appId + timestamp + key)

客户端拿着api_token 去访问不需要登录就能访问的接口

当访问用户需要登录的接口时,客户端跳转到登录页面,通过用户名和密码调用登录接口,登录接口会返回一个user_token, 客户端拿着user_token 去访问需要登录才能访问的接口

sign的作用是防止参数被篡改,客户端调用服务端时需要传递sign参数,响应客户端时也可以返回一个sign用于客户度校验返回的值是否被非法篡改了。客户端传的sign和端响应的sign算法可能会不同。

六:示例代码

1. dependency

org.springframework.boot spring-boot-starter-data-redis redis.clients jedis 2.9.0 org.springframework.boot spring-boot-starter-web

2. RedisConfiguration

@Configurationpublic class RedisConfiguration { @Bean public JedisConnectionFactory jedisConnectionFactory{ return new JedisConnectionFactory; } /** * 支持存储对象 * @return */ @Bean public RedisTemplate redisTemplate{ RedisTemplate redisTemplate = new StringRedisTemplate; redisTemplate.setConnectionFactoryjedisConnectionFactory Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializerObject.class ObjectMapper objectMapper = new ObjectMapper; objectMapper.setVisibilityPropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY objectMapper.enableDefaultTypingObjectMapper.DefaultTyping.NON_FINAL jackson2JsonRedisSerializer.setObjectMapperobjectMapper redisTemplate.setValueSerializerjackson2JsonRedisSerializer redisTemplate.afterPropertiesSet; return redisTemplate; }}

3. TokenController

4. WebMvcConfiguration

@Configurationpublic class WebMvcConfiguration extends WebMvcConfigurationSupport { private static final String excludePathPatterns = {“/api/token/api_token”}; @Autowired private TokenInterceptor tokenInterceptor; @Override public void addInterceptorsInterceptorRegistry registry { super.addInterceptorsregistry registry.addInterceptortokenInterceptor .addPathPatterns“/api/**” .excludePathPatternsexcludePathPatterns }}

5. TokenInterceptor

@Componentpublic class TokenInterceptor extends HandlerInterceptorAdapter { @Autowired private RedisTemplate redisTemplate; /** * * @param request * @param response * @param handler 访问的目标方法 * @return * @throws Exception */ @Override public boolean preHandleHttpServletRequest request, HttpServletResponse response, Object handler throws Exception { String token = request.getHeader“token”; String timestamp = request.getHeader“timestamp”; // 随机字符串 String nonce = request.getHeader“nonce”; String sign = request.getHeader“sign”; Assert.isTrueStringUtils.isEmptytoken && !StringUtils.isEmptytimestamp && !StringUtils.isEmptysign “参数错误”; // 获取超时时间 NotRepeatSubmit notRepeatSubmit = ApiUtil.getNotRepeatSubmithandler long expireTime = notRepeatSubmit == null ? 5 * 60 * 1000 : notRepeatSubmit.value; // 2. 请求时间间隔 long reqeustInterval = System.currentTimeMillis - Long.valueOftimestamp Assert.isTruereqeustInterval < expireTime, “请求超时,请重新请求”; // 3. 校验Token是否存在 ValueOperations tokenRedis = redisTemplate.opsForValue; TokenInfo tokenInfo = tokenRedis.gettoken Assert.notNulltokenInfo, “token错误”; // 4. 校验签名将所有的参数加进来,防止别人篡改参数 所有参数看参数名升续排序拼接成url // 请求参数 + token + timestamp + nonce String signString = ApiUtil.concatSignStringrequest + tokenInfo.getAppInfo.getKey + token + timestamp + nonce; String signature = MD5Util.encodesignString boolean flag = signature.equalssign Assert.isTrueflag, “签名错误”; // 5. 拒绝重复调用第一次访问时存储,过期时间和请求超时时间保持一致 只有标注不允许重复提交注解的才会校验 if notRepeatSubmit != null { ValueOperations signRedis = redisTemplate.opsForValue; boolean exists = redisTemplate.hasKeysign Assert.isTrueexists, “请勿重复提交”; signRedis.setsign, 0, expireTime, TimeUnit.MILLISECONDS } return super.preHandlerequest, response, handler }}

6. MD5Util

public class MD5Util { private static final String hexDigits【 = { “0” “1” “2” “3” “4” “5” “6” “7” “8” “9” “a” “b” “c” “d” “e” “f” }; private static String byteArrayToHexStringbyte b【 { StringBuffer resultSb = new StringBuffer; for int i = 0; i < b.length; i++ resultSb.appendbyteToHexStringb【i】 return resultSb.toString; } private static String byteToHexStringbyte b { int n = b; if n < 0 n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits【d1】 + hexDigits【d2】 } public static String encodeString origin { return encodeorigin, “UTF-8”; } public static String encodeString origin, String charsetname { String resultString = null; try { resultString = new Stringorigin MessageDigest md = MessageDigest.getInstance“MD5”; if charsetname == null .equalscharsetname resultString = byteArrayToHexStringmd.digestresultString .getBytes; else resultString = byteArrayToHexStringmd.digestresultString .getBytescharsetname } catch Exception exception { } return resultString; }}

7. @NotRepeatSubmit

/** * 禁止重复提交 */@TargetElementType.METHOD@RetentionRetentionPolicy.RUNTIMEpublic @interface NotRepeatSubmit { /** 过期时间,单位毫秒 **/ long value default 5000;}

8. AccessToken

@Data@AllArgsConstructorpublic class AccessToken { /** token */ private String token; /** 失效时间 */ private Date expireTime;}

9. AppInfo

@Data@NoArgsConstructor@AllArgsConstructorpublic class AppInfo { /** App id */ private String appId; /** API 秘钥 */ private String key;}

10. TokenInfo

@Datapublic class TokenInfo { /** token类型: 、 */ private Integer tokenType; /** App 信息 */ private AppInfo appInfo; /** 用户其他数据 */ private UserInfo userInfo;}

11. UserInfo

@Datapublic class UserInfo { /** 用户名 */ private String username; /** 手机号 */ private String mobile; /** 邮箱 */ private String email; /** 密码 */ private String password; /** 盐 */ private String salt; private AccessToken accessToken; public UserInfo(String username, String password, String salt) { this.username = username; this.password = password; this.salt = salt; }}

12. ApiCodeEnum

13. ApiResult

@Data@NoArgsConstructor@AllArgsConstructorpublic class ApiResult { /** 代码 */ private String code; /** 结果 */ private String msg;}

14. ApiUtil

public class ApiUtil { /** * 按参数名升续拼接参数 * @param request * @return */ public static String concatSignStringHttpServletRequest request { Map paramterMap = new HashMap; request.getParameterMap.forEachkey, value -> paramterMap.putkey, value【0】 // 按照key升续排序,拼接参数 Set keySet = paramterMap.keySet; String【 keyArray = keySet.toArraynew String【keySet.size】; Arrays.sortkeyArray StringBuilder sb = new StringBuilder; for String k : keyArray { // 或略掉的字段 if k.equals“sign” { continue; } if paramterMap.getk.trim.length > 0 { // 参数值为空,则不参与签名 sb.appendk.append“=”.appendparamterMap.getk.trim.append“&”; } } return sb.toString; } public static String concatSignStringMap map { Map paramterMap = new HashMap; map.forEachkey, value -> paramterMap.putkey, value; // 按照key升续排序,拼接参数 Set keySet = paramterMap.keySet; String【 keyArray = keySet.toArraynew String【keySet.size】; Arrays.sortkeyArray StringBuilder sb = new StringBuilder; for String k : keyArray { if paramterMap.getk.trim.length > 0 { // 参数值为空,则不参与签名 sb.appendk.append“=”.appendparamterMap.getk.trim.append“&”; } } return sb.toString; } /** * 获取方法上的@NotRepeatSubmit注解 * @param handler * @return */ public static NotRepeatSubmit getNotRepeatSubmitObject handler { if handler instanceof HandlerMethod { HandlerMethod handlerMethod = HandlerMethod handler; Method method = handlerMethod.getMethod; NotRepeatSubmit annotation = method.getAnnotationNotRepeatSubmit.class return annotation; } return null; }}

15. ApiResponse

本文相关词条概念解析:

接口

接口指的是MD产品具有哪些输入输出的接口。首先作为MD产品,耳机的输出接口自然是必须有的。除了基本的耳机输出接口之外,录放型产品还应该具有线路输入的接口,这样才能够把MD和其它播放设备相连接,把播放的音频输入MD并且将其录制到MD片上。而目前的NetMD产品还应具有USB接口,这样才能够和电脑相连接,从而能够进行文件的传输。有的产品还具有麦克风的接口,可以把外部的声音通过MD录制下来。

延伸 · 推荐

API安全接口安全设计很难?阿里大师一文讲解API攻防问题 所以你的API还在裸奔?且看阿里大师带你领悟安全的代码正确姿势!(附

看起来好像前后端分离是个浪潮,原来只有APP客户端会考虑这些,现在连Web都要考虑前后端分离 。这里面不得不谈的就是API的设计和安全性,这些个问题不解决好,将会给安全和性能带来很大威胁 。下面我也是...

网友评论

小朱朱815
小朱朱815
拐弯枪到底实用不实用?
2019-12-01 10:02 423
Orangela子
Orangela子
有淮南焦岗湖简介吗?
2019-11-27 14:01 21
八两金opi
八两金opi
完全就不担心把手机忘在家里,功能上该有的功能都有了
2019-12-03 18:09 45
niquanfang
niquanfang
如何以一段话为小说简介?
2019-12-02 01:39 162
ttxvp3915
ttxvp3915
生物资源丰富,数百种野生动植物栖息于此
2019-12-03 09:40 660
dlp159
dlp159
BIM常用软件简介及BIM如何自学?
2019-12-03 15:09 805
啊哦又来了
啊哦又来了
说什么荣华富贵,管什么清规戒律
2019-12-04 02:17 3

相关阅读

友情链接: 网站地图

为全球用户24小时提供全面及时的中文资讯

声明:本站不提供任何视听上传、存储服务,所有内容均来自正规视频站点所提供的公开引用资源,如有侵权信息请联系我们删除

COPYRIGHT © 2007-2018