七、接口版本控制
1、简介在springboot项目中,如果要进行restful接口的版本控制一般有以下几个方向:
基于path的版本控制基于header的版本控制在spring mvc下,url映射到哪个method是由requestmappinghandlermapping来控制的,那么我们也是通过requestmappinghandlermapping来做版本控制的。
2、path控制实现首先定义一个注解
@target({elementtype.method, elementtype.type})@retention(retentionpolicy.runtime)public @interface apiversion { // 默认接口版本号1.0开始,这里我只做了两级,多级可在正则进行控制 string value() default 1.0;}apiversioncondition用来控制当前request 指向哪个method
public class apiversioncondition implements requestcondition { private static final pattern version_prefix_pattern = pattern.compile(v(\\\\d+\\\\.\\\\d+)); private final string version; public apiversioncondition(string version) { this.version = version; } @override public apiversioncondition combine(apiversioncondition other) { // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义 return new apiversioncondition(other.getapiversion()); } @override public apiversioncondition getmatchingcondition(httpservletrequest httpservletrequest) { matcher m = version_prefix_pattern.matcher(httpservletrequest.getrequesturi()); if (m.find()) { string pathversion = m.group(1); // 这个方法是精确匹配 if (objects.equals(pathversion, version)) { return this; } // 该方法是只要大于等于最低接口version即匹配成功,需要和compareto()配合 // 举例:定义有1.0/1.1接口,访问1.2,则实际访问的是1.1,如果从小开始那么排序反转即可// if(float.parsefloat(pathversion)>=float.parsefloat(version)){// return this;// } } return null; } @override public int compareto(apiversioncondition other, httpservletrequest request) { return 0; // 优先匹配最新的版本号,和getmatchingcondition注释掉的代码同步使用// return other.getapiversion().compareto(this.version); } public string getapiversion() { return version; }}pathversionhandlermapping用于注入spring用来管理
public class pathversionhandlermapping extends requestmappinghandlermapping { @override protected boolean ishandler(class? beantype) { return annotatedelementutils.hasannotation(beantype, controller.class); } @override protected requestcondition? getcustomtypecondition(class? handlertype) { apiversion apiversion = annotationutils.findannotation(handlertype,apiversion.class); return createcondition(apiversion); } @override protected requestcondition? getcustommethodcondition(method method) { apiversion apiversion = annotationutils.findannotation(method,apiversion.class); return createcondition(apiversion); } private requestconditionwebmvcconfiguration配置类让spring来接管
@configurationpublic class webmvcconfiguration implements webmvcregistrations { @override public requestmappinghandlermapping getrequestmappinghandlermapping() { return new pathversionhandlermapping(); }}最后controller进行测试,默认是v1.0,如果方法上有注解,以方法上的为准(该方法vx.x在路径任意位置出现都可解析)
@restcontroller@apiversion@requestmapping(value = /{version}/test)public class testcontroller { @getmapping(value = one) public string query(){ return test api default; } @getmapping(value = one) @apiversion(1.1) public string query2(){ return test api v1.1; } @getmapping(value = one) @apiversion(3.1) public string query3(){ return test api v3.1; }}3、header控制实现总体原理与path类似,修改apiversioncondition即可,之后访问时在header带上x-version参数即可
public class apiversioncondition implements requestcondition { private static final string x_version = x-version; private final string version ; public apiversioncondition(string version) { this.version = version; } @override public apiversioncondition combine(apiversioncondition other) { // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义 return new apiversioncondition(other.getapiversion()); } @override public apiversioncondition getmatchingcondition(httpservletrequest httpservletrequest) { string headerversion = httpservletrequest.getheader(x_version); if(objects.equals(version,headerversion)){ return this; } return null; } @override public int compareto(apiversioncondition apiversioncondition, httpservletrequest httpservletrequest) { return 0; } public string getapiversion() { return version; }}八、api接口安全
1、简介app、前后端分离项目都采用[api]接口形式与服务器进行数据通信,传输的数据被偷窥、被抓包、被伪造时有发生,那么如何设计一套比较安全的api接口方案至关重要,一般的解决方案有以下几点:
token授权认证,防止未授权用户获取数据;时间戳超时机制;url签名,防止请求参数被篡改;防重放,防止接口被第二次请求,防采集;采用https通信协议,防止数据明文传输;2、token授权认证因为http协议是无状态的,token的设计方案是用户在客户端使用用户名和密码登录后,服务器会给客户端返回一个token,并将token以键值对的形式存放在缓存(一般是redis)中,后续客户端对需要授权模块的所有操作都要带上这个token,服务器端接收到请求后进行token验证,如果token存在,说明是授权的请求。
token生成的设计要求
应用内一定要唯一,否则会出现授权混乱,a用户看到了b用户的数据;每次生成的[token]一定要不一样,防止被记录,授权永久有效;一般token对应的是redis的key,value存放的是这个用户相关缓存信息,比如:用户的id;要设置token的过期时间,过期后需要客户端重新登录,获取新的token,如果[token]有效期设置较短,会反复需要用户登录,体验比较差,我们一般采用token过期后,客户端静默登录的方式,当客户端收到[token]过期后,客户端用本地保存的用户名和密码在后台静默登录来获取新的[token],还有一种是单独出一个刷新token的接口,但是一定要注意刷新机制和安全问题;根据上面的设计方案要求,我们很容易得到token=md5(用户id+登录的时间戳+服务器端秘钥)这种方式来获得token,因为用户id是应用内唯一的,登录的时间戳保证每次登录的时候都不一样,服务器端秘钥是配置在服务器端参与加密的字符串(即:盐),目的是提高token加密的破解难度,注意一定不要泄漏
3、时间戳超时机制客户端每次请求接口都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如:1分钟),则认为该请求失效。 时间戳超时机制是防御dos攻击的有效手段。 例如http://url/getinfo?id=1&timetamp=1661061696
4、url签名写过支付宝或微信支付对接的同学肯定对url签名不陌生,我们只需要将原本发送给server端的明文参数做一下签名,然后在server端用相同的算法再做一次签名,对比两次签名就可以确保对应明文的参数有没有被中间人篡改过。例如http://url/getinfo?id=1&timetamp=1559396263&sign=e10adc3949ba59abbe56e057f20f883e
签名算法过程
首先对通信的参数按key进行字母排序放入数组中(一般请求的接口地址也要参与排序和签名,那么需要额外添加url=http://url/getinfo这个参数)对排序完的数组键值对用&进行连接,形成用于加密的参数字符串在加密的参数字符串前面或者后面加上私钥,然后用md5进行加密,得到sign,然后随着请求接口一起传给服务器。服务器端接收到请求后,用同样的算法获得服务器的sign,对比客户端的sign是否一致,如果一致请求有效5、防重放客户端第一次访问时,将签名sign存放到服务器的redis中,超时时间设定为跟时间戳的超时时间一致,二者时间一致可以保证无论在timestamp限定时间内还是外 url都只能访问一次,如果被非法者截获,使用同一个url再次访问,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。
如果在缓存中的签名失效的情况下,有人使用同一个url再次访问,则会被时间戳超时机制拦截,这就是为什么要求sign的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保url被别人截获了也无法使用(如抓取数据)
方案流程
客户端通过用户名密码登录服务器并获取token;客户端生成时间戳timestamp,并将timestamp作为其中一个参数;客户端将所有的参数,包括token和timestamp按照自己的签名算法进行排序加密得到签名sign将token、timestamp和sign作为请求时必须携带的参数加在每个请求的url后边,例:http://url/request?token=h40adc3949bafjhbbe56e027f20f583a&timetamp=1559396263&sign=e10adc3949ba59abbe56e057f20f883e服务端对token、timestamp和sign进行验证,只有在token有效、timestamp未超时、缓存服务器中不存在sign三种情况同时满足,本次请求才有效;6、采用https通信协议安全套接字层超文本传输协议https,为了数据传输的安全,https在http的基础上加入了ssl协议,ssl依靠证书来验证服务器的身份,并为客户端和服务器之间的通信加密。
https也不是绝对安全的,比如中间人劫持攻击,中间人可以获取到客户端与服务器之间所有的通信内容
九、总结
自此整个后端接口基本体系就构建完毕了
通过validator + 自动抛出异常来完成了方便的参数校验通过全局异常处理 + 自定义异常完成了异常操作的规范通过数据统一响应完成了响应数据的规范多个方面组装非常优雅的完成了后端接口的协调,让开发人员有更多的经历注重业务逻辑代码,轻松构建后端接口这里再说几点
controller做好try-catch工作,及时捕获异常,可以再次抛出到全局,统一格式返回前端做好日志系统,关键位置一定要有日志做好全局统一返回类,整个项目规范好定义好controller入参字段可以抽象出一个公共基类,在此基础上进行继承扩充controller层做好入参参数校验接口安全验证
医疗影像出现新的格局,“圈地时代”
基于AWS IoT服务的地产与建筑行业解决方案与成功案例
海尔免清洗全自动3公斤小小神童洗衣机,专为宝宝健康而生
E开箱:智能音箱新曝光
图像处理器的基本概念
SpringBoot 后端接口规范(下)
亿纬锂能违规暴雷!
苹果手机的几大缺点
如何鉴定户外LED广告屏的质量好坏
小马智行荣膺首个跨省自动驾驶重卡示范应用许可企业
研究人员以极高的精度控制单个光量子
比特币扩容之争的缘由阐述
pcb半导体对高速信号传输的挑战
手机无线充电技术或许会对电池造成损伤
中国电信与华为携手打造省干光立方网络
对于储能行业合理补偿机制是否能够撬动市场需求
Mybatis-plus批量插入太慢?
薄膜电容器的最新发展趋势
三相可控硅半控桥数字触发器的设计
雷士照明斥5亿收购蔚蓝芯光60%股权