0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

SpringBoot 后端接口规范(下)

jf_78858299 来源:架构师 作者:架构师 2023-05-05 17:02 次阅读

七、接口版本控制

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<ApiVersionCondition> {
    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 RequestCondition

WebMvcConfiguration配置类让spring来接管

@Configuration
public 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<ApiVersionCondition> {
    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层做好入参参数校验
  • 接口安全验证
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7639

    浏览量

    148485
  • URL
    URL
    +关注

    关注

    0

    文章

    134

    浏览量

    14827
  • 后端
    +关注

    关注

    0

    文章

    29

    浏览量

    2095
  • SpringBoot
    +关注

    关注

    0

    文章

    172

    浏览量

    106
收藏 人收藏

    评论

    相关推荐

    请问可变增益放大器AD8369后端接模数转换器AD9268怎么匹配?

    如题,想请教一可变增益放大器AD8369后端接模数转换器AD9268怎么匹配?AD9268前接50欧阻抗的手册里面有(下图),可AD8369输出为200欧阻抗,不知道该怎么接?不知道差分阻抗匹配怎么计算?@
    发表于 12-25 11:40

    SpringBoot中的Druid介绍

    SpringBoot中Druid数据源配置
    发表于 05-07 09:21

    SpringBoot知识总结

    SpringBoot干货学习总结
    发表于 08-01 10:40

    怎么学习SpringBoot

    SpringBoot学习之路(X5)- 整合JPA
    发表于 06-10 14:52

    如何检测光耦后端接了负载

    我现在要做一个用光耦控制窗帘电机,并且能够检测到电机有没接到光耦后端,求大神指导检测电路该怎么设计
    发表于 07-22 13:22

    端接口与差动接口的区别在哪里?

    端接口与差动接口的区别在哪里?
    发表于 05-18 06:35

    怎样去使用springboot

    怎样去使用springboot呢?学习springboot需要懂得哪些?
    发表于 10-25 07:13

    SpringBoot应用启动运行run方法

    什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;获取嵌入式的Servlet容器工厂:1)、SpringBoot应用启动运行run方法2
    发表于 12-20 06:16

    数字接口—单端接口与差动接口的对比

    数字接口—单端接口与差动接口的对比
    发表于 11-07 08:07 0次下载
    数字<b class='flag-5'>接口</b>—单<b class='flag-5'>端接口</b>与差动<b class='flag-5'>接口</b>的对比

    什么是 SpringBoot

    本文从为什么要有 `SpringBoot`,以及 `SpringBoot` 到底方便在哪里开始入手,逐步分析了 `SpringBoot` 自动装配的原理,最后手写了一个简单的 `start` 组件,通过实战来体会了 `
    的头像 发表于 04-07 11:28 1026次阅读
    什么是 <b class='flag-5'>SpringBoot</b>?

    SpringBoot 后端接口规范(上)

    一个后端接口大致分为四个部分组成: 接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response) 。虽然说后端接口的编写并没有统一
    的头像 发表于 05-05 17:00 525次阅读
    <b class='flag-5'>SpringBoot</b> <b class='flag-5'>后端接口</b><b class='flag-5'>规范</b>(上)

    SpringBoot 后端接口规范(中)

    一个后端接口大致分为四个部分组成: 接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response) 。虽然说后端接口的编写并没有统一
    的头像 发表于 05-05 17:01 467次阅读
    <b class='flag-5'>SpringBoot</b> <b class='flag-5'>后端接口</b><b class='flag-5'>规范</b>(中)

    后端分离必备的接口规范

    随着互联网的高速发展,前端页面的展示、交互体验越来越灵活、炫丽,响应体验也要求越来越高,后端服务的高并发、高可用、高性能、高扩展等特性的要求也愈加苛刻,从而导致前后端研发各自专注于自己擅长的领域深耕细作。
    的头像 发表于 05-15 17:16 513次阅读
    前<b class='flag-5'>后端</b>分离必备的<b class='flag-5'>接口</b><b class='flag-5'>规范</b>

    springboot后端交互流程

    Boot 进行开发时,前后端交互是一个非常重要的部分,本文将详细介绍 Spring Boot 前后端交互的流程。 前后端交互的基本原理 在前后端交互的过程中,前端负责向
    的头像 发表于 11-22 16:00 796次阅读

    一个注解搞定SpringBoot接口防刷

    技术要点:springboot的基本知识,redis基本操作,
    的头像 发表于 11-28 10:46 221次阅读