This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

扩展能力

Restlight内置了常用的Filter(IP白名单、新建连接数限制、CPU过载保护)、Interceptor(访问日志、参数签名验证)和表单参数解析器

可以一次引入所有扩展能力


<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

1 - 新建连接数限制

使用新建连接数限制时请先确保引入了依赖:

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-filter-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

当前服务的新建连接数进行QPS限制。超过连接数限制的请求将被拒绝。

使用方式:

#开启新建连接数限制
restlight.server.ext.connection-creation-limit.enable=true
#设置每秒限制4000个新建连接,默认为20000
restlight.server.ext.connection-creation-limit.max-per-second=40000

2 - CPU Load保护

当服务宿主机Cpu负载达到一定阈值之后开始随机丢弃连接(新建连接, 已经建立的连接不受影响)

使用新建连接数限制时请先确保引入了依赖:

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-filter-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>
#开启Cpu Load自我保护
restlight.server.ext.cpu-load-protection.enable=true
#cpu负载阈值,默认为80.0D cpu超过此负载之后将开始随机丢弃连接
restlight.server.ext.cpu-load-protection.threshold=80.0D
#初始连接丢弃率,默认为10.0D(0代表0%, 100代表100%, 可以传小数)
restlight.server.ext.cpu-load-protection.initial-discard-rate=10.0D
#最大连接丢弃率,默认为80.0D(0代表0%, 100代表100%, 可以传小数)
restlight.server.ext.cpu-load-protection.max-discard-rate=80.0D

上面的配置将会在cpu负载到达75%时开始随机丢弃20%的新建连接, 随着cpu负载的升高达到100%则将会丢弃80%的连接。

说明:

当cpu负载到达或者超过cpu-load-threshold的值时开始丢弃连接,初始连接丢弃概率为initial-discard-rate, 随着cpu负载升高, 丢弃率将随着cpu负载的升高而成正比的升高, 当cpu负载达到100%时丢弃率将达到max-discard-rate.

3 - Access Log

使用Restlight访问日志拦截器时请确保已经引入了依赖:

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-filter-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

Quick Start

AccessLog拦截器在每个请求结束后记录访问日志,内容包含:客户端地址、请求协议、请求url(不包含路径参数)、请求方法、请求耗时、响应状态码、响应body大小以及访问时间。使用时需要做如下配置:

#开启AccessLog
restlight.server.ext.accesslog.enable=true

配置

配置项 默认 说明
enable false 是否启用
directory logs 日志文件路径
fileName access.log 日志文件名
charset 日志编码
rolling true 是否按照时间滚动生成文件
date-pattern yyyy-MM-dd 日期滚动格式,yyyy-MM-dd表示按天为单位滚动,生成的文件名为access.yyyy-MM-dd.log, 仅支持按天和小时为单位滚动,因此可选值:yyyy-MM-dd或者yyyy-MM-dd_HH(注意不要使用yyyy-MM-dd HH, 生成的文件名可能不符合操作系统文件命名规范)
max-history 10 最大历史文件个数
full-uri false 是否打印uri中所有的内容(包含url参数)

4 - 跨域

引入依赖

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-filter-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

开启跨域功能

restlight.server.ext.cors.enable=true

更多跨域相关配置

restlight.server.ext.cors.rules[0].anyOrigin=false
restlight.server.ext.cors.rules[0].origins=www.example.com,www.demo.com
restlight.server.ext.cors.rules[0].expose-headers=foo,bar
restlight.server.ext.cors.rules[0].allow-credentials=false
restlight.server.ext.cors.rules[0].allow-methods=GET,POST
restlight.server.ext.cors.rules[0].allow-headers=foo,bar
restlight.server.ext.cors.rules[0].max-age=3600

5 - 文件及表单参数解析

Restlight提供了表单参数解析的功能,使用时需要单独引入相应的包:

<dependency>
   <groupId>io.esastack</groupId>
   <artifactId>restlight-ext-multipart-starter</artifactId>
   <version>${restlight.version}</version>
</dependency>

文件参数

使用示例如下:

@Controller
@RequestMapping("/restlight/file/")
public class FileSupportController
    // 上传单个文件
    @RequestMapping("/upload")
    public String fileUpload(@UploadFile MultipartFile multipartFile) throws IOException {
        File temp = new File("D:\\" + multipartFile.originalFilename());
        multipartFile.transferTo(temp);
        return "SUCCESS";
    }
	
    // 上传一组文件
    @RequestMapping("/uploads")
    public String fileUploads(@UploadFile List<MultipartFile> files) throws IOException {
        for (MultipartFile file : files) {
            File temp = new File("D:\\" + file.originalFilename());
            file.transferTo(temp);
        }
        return "SUCCESS";
    }
}

Restlight默认请求body大小为4MB,当上传文件时需要根据需要调整该值的大小;默认的编码格式为:UTF-8。使用时,也可以通过配置文件改变上述参数值:

#设置请求body大小 4MB = 4 * 1024 * 1024 = 4194304
restlight.server.max-content-length=4194304

#编码方式
restlight.server.ext.multipart.charset=utf-8

#单个文件大小限制,默认-1(没有限制) 4KB = 4 * 1024 = 4096
restlight.server.ext.multipart.max-size=4096

#是否使用临时文件,为true时任何大小的文件都使用临时文件,默认为false
restlight.server.ext.multipart.use-disk=true

#临时文件目录
restlight.server.ext.multipart.temp-dir=D:\\temp

#当multipart-use-disk为false且单个文件大小超过该值时使用临时文件,默认2MB
restlight.server.ext.multipart.memory-threshold=2097152

非文件参数

在需要接收的方法参数上加上@FormParam注解,如下:

@RequestMapping("/upload")
public String uploadFormParams(@FormParam String formParam0, @FormParam String formParam1) {
    return formParam0 + "; " + formParam1;
}

6 - 签名认证

使用Restlight参数签名验证拦截器时请先引入依赖:

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-interceptor-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

参数签名验证拦截器可以验证请求参数的签名,防止请求参数被篡改。使用时请做如下配置:

#开启参数签名验证功能的必需配置
restlight.server.ext.sign.enable=true

#调用方ID参数名,默认为appId
restlight.server.ext.sign.app-id-name=appId

#签名秘钥版本参数名,默认为sv
restlight.server.ext.sign.secret-version-name=sv

#请求时间戳参数名,默认为ts
restlight.server.ext.sign.timestamp-name=ts

#请求时间戳有效期:单位秒(默认为0)
restlight.server.ext.sign.expire-seconds=0

#签名参数名称,默认为sign
restlight.server.ext.sign.signature-name=sign

#是否对所有接口进行签名验证(默认为false)
restlight.server.ext.sign.verify-all=true

服务端验签详细过程

  • 第一步:从请求中获取签名值signature并去掉前后空格(先从url参数中获取,如果没有再从请求头中获取);
  • 第二步:从请求中获取时间戳timestamp并去掉前后空格(方式同上),如果配置了请求时间戳有效期,则判断是否在有效期内。
  • 第三步:从请求中获取appId、secretVersion并去掉前后空格(方式同上)。
  • 第四步:根据上述的appId、secretVersion、timestamp从自定义的SecretProvider获取secret。
  • 第五步:根据请求参数构建签名data[],具体步骤为:1.构建请求参数对应的paramData[]:获取所有的url参数(排除sign),按照参数名字典序升序排列,若一个参数对应多个值,则这多个值也按字典序升序排列(注意:所有参数名和参数值均会去掉前后空格)。如:http://api.xxx.com/getUserInfo?appId=your_appId&sv=1&ts=1555933697000&user_id=u001&sign=xxx&names=LiMing&names=ZhangSan对应的paramData[]=(“api_key=your_appId&names=LiMing&names=ZhangSan&sv=1&t=1555933697000&user_id=u001”).getBytes(“UTF-8”);2.构建请求body对应的bodyData[]:对于类型为POST且Content-Type不包含x-www-form-urlencoded的请求,直接通过request.getBody()获取bodyData[]。重要说明: 对于Content-Type包含x-www-form-urlencoded的POST请求,验证签名时会将body中参数合并到url的参数中一起处理,客户端加密时需要注意此种情况;3.合并paramData[]和bodyData[]作为签名data[]。
  • 第六步:使用HmacSha1算法生成data[]与secret的签名(详见esa-commons项目下SecurityUtils的getHmacSHA1方法)。
  • 第七步:验证signature与第六步生成的签名是否相等。

指定或排除需要进行签名验证的接口

Restlight提供了两种不同的方式来自定义需要进行签名验证的接口:1. 在全局接口都进行签名验证的情况下,使用@IgnoreSignValidation注解忽略指定接口的签名验证功能;2. 在全局接口都不进行签名验证的前提下,使用@SignValidation注解指定对需要进行签名验证的接口。默认使用方式2。 方式1使用示例:

restlight.server.ext.sign.verify-all=true
@RequestMapping("/index")
@IgnoreSignValidation
public void index() {
    TestService.list();
}

如上配置表示:对index()方法之外的其他接口均开启签名验证功能。

方式2使用示例:

restlight.server.ext.sign.verify-all=false
@RequestMapping("/index")
@SignValidation
public void index() {
    TestService.list();
}

如上配置表示:只对index()方法对应的接口开启签名验证功能。

自定义参数签名验证拦截器

Restlight默认使用HmacSHA1作为验签时原始请求的签名生成方法,当用户使用其它算法可以注入自定义的参数签名验证拦截器,使用示例如下:

@Component
public class CustomizeSignatureValidationFactory extends SignValidationHandlerInterceptorFactory {

    public CustomizeSignatureValidationFactory(SecretDistributor distributor) {
        super(distributor);
    }

    @Override
    protected AbstractSignatureRouteInterceptor doCreate(SignatureOptions options, SecretDistributor distributor) {
        return new AbstractSignatureRouteInterceptor(options, distributor) {
            @Override
            protected boolean validate(byte[] data, String signature, String sk) {
                // customize validation
            }
        };
    }
}

7 - XSS过滤

引入依赖:

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-filter-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

使用方式:

#开启Xss过滤
restlight.server.ext.xss.enable=true
#Xss过滤模式,默认escape(转义模式),filter为过滤模式
restlight.server.ext.xss.mode=escape

配置好后自动对所有请求进行转义或者过滤。

XSS过滤范围

支持URL参数过滤及不完整Header过滤

Escape & Filter模式

Escape 模式

该模式会对用户请求的 URL参数 和 Header 进行转义,转义的字符集如下:

转义前 转义后
> &gt;
< &lt;
" &quot;
& &amp;

Filter 模式

该模式会对用户请求的 URL参数 和 Header 进行过滤,删除容易引起 Xss 的标签或者表达式,以空串代替,比如 name=<script>...</script> ,过滤以后会直接将 <script>...</script> 以空串替换,即 name="",需要以空串替换的标签和表达式如下:

标签或表达式
<script>…</script>
</script>
<script …>
src='…'
src="…"
eval(…)
e­xpression(…)
javascript:
alert
onload=
vbscript:

8 - IP白名单

IP白名单

使用Restlight内置的IP白名单时请先确保引入了依赖:

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-filter-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

IP白名单拦截器可以过滤非法IP的访问。同时支持IP地址和正则表达式两种匹配方式,需要配置的内容如下:

#开启IP白名单拦截器的必需配置
restlight.server.ext.whitelist.enable=true

#IP白名单列表(多值请用逗号分隔,正则表达式regex:开头)
restlight.server.ext.whitelist.ips=10.10.1.1,regex:10.12.*

#缓存最近访问的IP地址(默认1024个)
restlight.server.ext.whitelist.cache-size=1024

#缓存的失效时间(单位:ms,默认为60s)
restlight.server.ext.whitelist.expire=60000

9 - 数据校验

Restlight集成了Hibernate Validator,提供了开箱即用的数据校验功能,通过注解完成对JavaBean、Controller方法参数和返回值的校验, 并支持异常消息国际化。

使用Restlight内置的数据校验请先确保引入了依赖:

<dependency>
	<groupId>io.esastack</groupId>
	<artifactId>restlight-ext-validator-starter</artifactId>
	<version>${restlight.version}</version>
</dependency>

普通JavaBean的校验

使用注解声明对属性的约束

private class Employee {
	@NotEmpty
	private String name;
	
	@Min(18)
	@Max(60)
	private int age;
	
	@Email
	private String email;
	
	@Length(min = 10, max = 20)
	private String address;

        // 级联校验
	@Valid
	private Object cascadingObject;
}

作为方法参数校验, 需要使用@Valid注解标记被校验的参数

@PostMapping("/add")
public String add(@Valid @RequestBody Employee employee) {
    return SUCCESS;
}

作为返回值, 需要使用@Valid注解标记方法或者参数

@Valid
@ResponseBody
@RequestMapping("/list")
public Employee list1() {
    return new Employee("", 16, "", "");
}

或者

@ResponseBody
@RequestMapping("/list")
public @Valid Employee list2() {
   return new Employee("", 16, "", "");
}

普通方法参数校验

直接使用注解

@RequestMapping("/update")
public String update(@RequestParam @NotEmpty String name, @RequestParam @Length(min = 10, max = 20) String newAddress) {
    return SUCCESS;
}

分组校验

使用@ValidGroup指定校验方法的参数、返回值校验时的分组。该注解只能标注在方法上并且value值只能为接口类(默认为Default.class)。

Example:

@ValidGroup(Interface.class)
@RequestMapping("/addGroup")
public String addGroup(@Valid @RequestBody Employee employee) {
    return SUCCESS;
}

自定义约束注解

当内置的约束注解不能满足业务需求时,可以使用@Constraint自定义约束注解,具体实现使用hibernate-validation,使用方式与Spring MVC无差异,示例如下:

自定义约束注解:

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = LogInSuccess.LogInSuccessValidator.class)
@Target(value = {ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Documented
public @interface LogInSuccess {

    String message() default "登录校验未通过";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    class LogInSuccessValidator implements ConstraintValidator<LogInSuccess, String> {

        @Override
        public void initialize(LogInSuccess constraintAnnotation) {
            // Do nothing
        }

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            return false;
        }
    }
}

使用自定义约束注解:

@RequestMapping("/getId")
public String getId(@RequestParam @LogInSuccess(message = "请先登录") String userName) {
    return "SUCCESS";
}

国际化

数据校验的异常消息允许自定义并且支持国际化,自定义异常处理消息的步骤如下:

自定义异常消息文件,在classpath路径下加入配置文件,如validation-message.properties

key1=value1
key2=value2
...

配置异常消息文件名,在application中配置异常消息文件名,如:

#该文件名称对应上面定义的validation-message.properties文件
restlight.server.ext.validation.message-file=validation-message

修改约束注解的message属性值,如:

@NotEmpty(message="{key1}")
public String name;

@Min(value=18, message="{key2}")
public int age;

针对不同语言定义不同的异常消息文件,如:

  • validation-message_zh_CN.properties
  • validation-message_en.properties
  • validation-message_cs.properties
  • validation-message_en.properties
  • ……

数据校验注解一览

注解 功能 说明
@AssertFalse 被注解元素必须为false
@AssertTrue 被注解的元素必须为true
@DecimalMax(value) 被注解的元素必须为一个数字,其值必须小于等于指定的最小值
@DecimalMin(Value) 被注解的元素必须为一个数字,其值必须大于等于指定的最小值
@Digits(integer=, fraction=) 被注解的元素必须为一个数字,其值必须在可接受的范围内
@Future 被注解的元素必须是未来的日期
@Max(value) 被注解的元素必须为一个数字,其值必须小于等于指定的最大值
@Min(value) 被注解的元素必须为一个数字,其值必须大于等于指定的最小值
@NotNull 被注解的元素必须不为null
@Null 被注解的元素必须为null
@Past 被注解的元素必须过去的日期
@Pattern 被注解的元素必须符合正则表达式
@Size(min=, max=) 被注解的元素必须在指定的范围(数据类型:String, Collection, Map and arrays)
@Email 被注解的元素被注释的元素必须是电子邮箱地址
@NotBlank 被注解的对象必须为字符串,不能为空,检查时会忽略空格
@NotEmpty 被注释的对象长度不能为0(数据:String,Collection,Map,arrays)
@Length(min=, max=) 被注解的对象必须是字符串并且长度必须在指定的范围内 Hibernate扩展注解
@Range(min=, max=) 被注释的元素必须在合适的范围内 (数据:BigDecimal, BigInteger, String, byte, short, int, long and 原始类型的包装类 ) Hibernate扩展注解
@URL(protocol=, host=, port=, regexp=, flags=) 被注解的对象必须是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件 Hibernate扩展注解