电子说
七、接口版本控制
在SpringBoot项目中,如果要进行restful接口的版本控制一般有以下几个方向:
在spring MVC下,url映射到哪个method是由RequestMappingHandlerMapping
来控制的,那么我们也是通过RequestMappingHandlerMapping
来做版本控制的。
首先定义一个注解
@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";
}
}
总体原理与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接口安全
APP、前后端分离项目都采用[API]接口形式与服务器进行数据通信,传输的数据被偷窥、被抓包、被伪造时有发生,那么如何设计一套比较安全的API接口方案至关重要,一般的解决方案有以下几点:
因为HTTP协议是无状态的,Token的设计方案是用户在客户端使用用户名和密码登录后,服务器会给客户端返回一个Token,并将Token以键值对的形式存放在缓存(一般是Redis)中,后续客户端对需要授权模块的所有操作都要带上这个Token,服务器端接收到请求后进行Token验证,如果Token存在,说明是授权的请求。
Token生成的设计要求
根据上面的设计方案要求,我们很容易得到Token=md5(用户ID+登录的时间戳+服务器端秘钥)这种方式来获得Token,因为用户ID是应用内唯一的,登录的时间戳保证每次登录的时候都不一样,服务器端秘钥是配置在服务器端参与加密的字符串(即:盐),目的是提高Token加密的破解难度,注意一定不要泄漏
客户端每次请求接口都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如:1分钟),则认为该请求失效。 时间戳超时机制是防御DOS攻击的有效手段。 例如http://url/getInfo?id=1&timetamp=1661061696
写过支付宝或微信支付对接的同学肯定对URL签名不陌生,我们只需要将原本发送给server端的明文参数做一下签名,然后在server端用相同的算法再做一次签名,对比两次签名就可以确保对应明文的参数有没有被中间人篡改过。例如http://url/getInfo?id=1&timetamp=1559396263&sign=e10adc3949ba59abbe56e057f20f883e
签名算法过程
url=http://url/getInfo
这个参数)客户端第一次访问时,将签名sign存放到服务器的Redis中,超时时间设定为跟时间戳的超时时间一致,二者时间一致可以保证无论在timestamp限定时间内还是外 URL都只能访问一次,如果被非法者截获,使用同一个URL再次访问,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。
如果在缓存中的签名失效的情况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截,这就是为什么要求sign的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)
方案流程
http://url/request?token=h40adc3949bafjhbbe56e027f20f583a&timetamp=1559396263&sign=e10adc3949ba59abbe56e057f20f883e
安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为客户端和服务器之间的通信加密。
HTTPS也不是绝对安全的,比如中间人劫持攻击,中间人可以获取到客户端与服务器之间所有的通信内容
九、总结
自此整个后端接口基本体系就构建完毕了
这里再说几点
全部0条评论
快来发表一下你的评论吧 !