```xml
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-security-validation</artifactId>
</dependency>
```

### 1、描述

安全扩展插件，为 solon 提供完整的数据校验能力。支持：

* 控制器方法注解校验
  * 支持 Header，Param，Cookie，Body，IP 等...校验
  * 支持行为验证，比如重复提交（需要对接）
  * 支持身份验证，比如白名单（需要对接）
* 参数注解校验
* 实体注解校验


默认策略，有校验不通过的会马上返回。如果校验所有，需加配置声明（返回的信息结构会略不同）：

```yaml
solon.validation.validateAll: true
```

能力实现与说明：


| 加注位置 | 能力实现基础 | 说明 |
| -------- | -------- | -------- |
| 函数     | 基于 `@Addition(Filter.class)` 实现     | 对请求上下文做校验（属于注入前校验）     |
| 参数     | 基于 `@Around(Interceptor.class)` 实现     | 对函数的参数值做校验（属于注入后校验）     |




### 2、校验注解清单



| 注解  | 作用范围 |  说明 | 
| -------- | -------- | -------- | 
| Valid | 控制器类 | 启用校验能力（加在控制器或者控制器基类上） |
|   | | |
| Validated | 参数 或 字段 | 校验（参数或字段的类型）实体类上的字段    | 
|   | | |
| Date    | 参数 或 字段 | 校验注解的值为日期格式    | 
| DecimalMax(value)    | 参数 或 字段 | 校验注解的值小于等于@ DecimalMax指定的value值     | 
| DecimalMin(value)     | 参数 或 字段 | 校验注解的值大于等于@ DecimalMin指定的value值     | 
| Email    | 参数 或 字段 | 校验注解的值为电子邮箱格式    | 
| Length(min, max)    | 参数 或 字段 | 校验注解的值长度在min和max区间内（对字符串有效）     | 
| Logined    |  控制器 或 动作 | 校验本次请求主体已登录     | 
| Max(value)    |  参数 或 字段 | 校验注解的值小于等于@Max指定的value值     | 
| Min(value)     | 参数 或 字段 | 校验注解的值大于等于@Min指定的value值     | 
| NoRepeatSubmit    | 控制器 或 动作  | 校验本次请求没有重复提交     | 
| NotBlacklist    | 控制器 或 动作 | 校验本次请求主体不在黑名单     | 
| NotBlank    | 动作 或 参数 或 字段 | 校验注解的值不是空白     | 
| NotEmpty    | 动作 或 参数 或 字段 | 校验注解的值不是空     | 
| NotNull   | 动作 或 参数 或 字段 | 校验注解的值不是null     | 
| NotZero  | 动作 或 参数 或 字段 | 校验注解的值不是0     | 
| Null    | 动作 或 参数 或 字段 | 校验注解的值是null     | 
| Numeric    | 动作 或 参数 或 字段 | 校验注解的值为数字格式    | 
| Pattern(value)    | 参数 或 字段 | 校验注解的值与指定的正则表达式匹配    | 
| Size   | 参数 或 字段 | 校验注解的集合大小在min和max区间内（对集合有效）    | 
| Whitelist    | 控制器 或 动作 | 校验本次请求主体在白名单范围内     | 


### 3、`@Valid` 与 `@Validated` 的区别

* Valid

用于启用校验能力。加在控制器或者控制器基类上。

* Validated

校验（参数或字段的类型）实体类上的字段。加在参数或字段上。


### 4、应用示例

* 注解触发校验

```java
//可以加在方法上、或控制器类上（或者控制器基类上）
@Valid
@Controller
public class UserController {
    //
    //这里只是演示，用时别乱加
    //
    @NoRepeatSubmit  //重复提交验证（加上方法上的，为注入之前校验）
    @Whitelist     //白名单验证（加上方法上的，为注入之前校验）
    @Mapping("/user/add")
    public void addUser(
            @NotNull String name, 
            @Pattern("^http") String icon, //注解在参数或字段上时，不需要加 value 属性
            @Validated User user) //实体校验，需要加 @Validated
    { 
        //...
    }
    
    //分组校验
    @Mapping("/user/update")
    public void updateUser(@Validated(UpdateLabel.class) User user){
        //...
    }
}

@Data
public class User {
    @NotNull(groups = UpdateLabel.class) //用于分组校验
    private Long id;
    
    @NotNull
    private String nickname;
    
    @Email  //注解在参数或字段上时，不需要加 value 属性
    private String email;
}
```

* 手动校验工具


```java
User user = new User();
ValidUtils.validateEntity(user);
```

### 5、需要业务检测对接的四个注解

* @NoRepeatSubmit 不能重复提交注解

```java
//示例：通过组件模式定义检测器（或通过配置器生产Bean）
@Component
public class NoRepeatSubmitCheckerImpl implements NoRepeatSubmitChecker {
    @Override
    public boolean check(NoRepeatSubmit anno, Context ctx, String submitHash, int limitSeconds) {
        //借用分布式锁，挡住 submitHash 在一定时间内多次进入
        return LockUtils.tryLock(Solon.cfg().appName(), submitHash, limitSeconds);
    }
}
```

* @Whitelist 白名单注解（即在一个名单里）

```java
//示例：通过组件模式定义检测器（或通过配置器生产Bean）
@Component
public class WhitelistCheckerImpl implements WhitelistChecker {
    @Override
    public boolean check(Whitelist anno, Context ctx) {
        String ip = ctx.realIp(); //此处以ip为例，其实也可以是任何东西

        //借用业务系统的名单列表，进行检测
        return CloudClient.list().inListOfIp("whitelist",ip);
    }
}
```



* @NotBlacklist 非黑名单注解（即不在一个名单里）

```java
//示例：通过组件模式定义检测器（或通过配置器生产Bean）
@Component
public class NotBlacklistCheckerImpl implements NotBlacklistChecker {
    @Override
    public boolean check(NotBlacklist anno, Context ctx) {
        String ip = ctx.realIp(); //此处以ip为例，其实也可以是任何东西

        //借用业务系统的名单列表，进行检测
        return CloudClient.list().inListOfIp("blacklist",ip) == false;
    }
}
```


* @Logined 已登录注解

```java
//示例：通过组件模式定义检测器（或通过配置器生产Bean）
@Component
public class LoginedCheckerImpl implements LoginedChecker {

    @Override
    public boolean check(Logined anno, Context ctx, String userKeyName) {
        return ctx.sessionAsLong(Constants.SESSION_USER_ID) > 0;
    }
}
```

### 6、更多使用说明

参考：[《请求参数校验、及定制与扩展》](/article/49)



