山海Guard

基于SpringBoot 的通用Web安全组件

Based SpringBoot Web Security components

GitHub release (latest by date) GitHub closed issues GitHub top language
GitHub Code Size GitHub Code Lines GitHub License

# 组件能力

  • 支持文件上传安全检测 (支持全局校验、单个方法校验、自定义校验、ZIP扫描校验等多种策略)
  • 支持基于Mybatis-Plus在进行数据字段级加密与脱敏(支持自行扩展实现相关算法,如对接密码机或加密服务)
  • 支持SQL注入&XSS注入安全检测
  • 支持密码复杂度检验 (支持数字、大小写字母、键盘排序、长度、相同字符、连续字符等多种策略)
  • 支持SpringBoot POST请求报文加密、响应报文加密
  • 支持SpringBoot 响应报文进行字段级数据脱敏(支持自行实现脱敏算法)
  • 支持对SpringBoot配置文件任意参数进行参数加密(支持自行扩展加密算法)
  • 支持对全量参数进行敏感词检查与过滤

# 1.引入依赖

        <dependency>
            <groupId>com.wangshanhai.guard</groupId>
            <artifactId>shanhai-guard-spring-boot-starter</artifactId>
            <version>${shanhaiguard.last.version}</version>
        </dependency>

# 2.启用ShanHaiGuard安全防护组件

@Configuration
@EnableShanHaiGuard
public class ShanHaiGuardConfig {
}

# 3.组件说明

# 3.1 文件上传检测

配置参数如下

shanhai:
  fileguard:
    enable: true  #启用组件
    pathPatterns: #检测范围
      - '/**'
    suffix: jpg,gif,png,ico,bmp,jpeg #文件上传白名单
    logTarce: true  #启用跟踪日志

此处的配置为全局性配置,对于单个方法的校验,可以使用@FileGuard添加校验,参考下面样例的使用方式:

1)跳过检验

@FileGuard(skip  = true)

2)实现自定义规则检验

@FileGuard(checkByRule = true)

注:如果启用该参数,需要实现FileGuardRuleDefService,否则将无法上传文件。

public class FileGuardRuleDefServiceImpl implements FileGuardRuleDefService {
    /**
     * 表单key及对应的文件
     * @param files 文件清单
     * @return
     */
    @Override
    public boolean isSafe(Map<String, MultipartFile> files) {
        return true;
    }
}

3)通过文件后缀校验(GuardType.SUFFIX)

/**
 * 文件上传测试样例
 * @param request
 * @return
 */
@RequestMapping(value = "/file/suffix/upload")
@FileGuard(message = "只能上传图片文件",type = FileGuard.GuardType.SUFFIX,supportedSuffixes = {"png", "jpg", "jpeg"})
@ResponseBody
public String suffix(HttpServletRequest request, MultipartFile file){
    Logger.info("[file-upload-api]-name:{},contentType:{},size:{}",file.getName(),file.getContentType(),file.getSize());
    return "success";
}

4)通过二进制文件头检验(GuardType.BYTES)

/**
 * 文件上传测试样例
 * @param request
 * @return
 */
@RequestMapping(value = "/file/bytes/upload")
@FileGuard(message = "只能上传图片文件",type = FileGuard.GuardType.BYTES,supportedFileTypes = {FileType.JPEG,FileType.PNG})
@ResponseBody
public String bytes(HttpServletRequest request, MultipartFile file){
    Logger.info("[file-upload-api]-name:{},contentType:{},size:{}",file.getName(),file.getContentType(),file.getSize());
    return "success";
}

注:单个方法的白名单必须为全局性白名单的子集

5)支持ZIP压缩包自动扫描检测

shanhai:
  fileguard:
    zipScan: true  #启用组件
    zipSafeSuffixs: jpg,gif,png,ico,bmp,jpeg #ZIP压缩包内文件上传白名单

6)排除某些特定URI校验

shanhai:
  fileguard:
     excludePathPatterns: 
      - '/**'

# 3.2 密码复杂度检测

配置参数如下

shanhai:
  passwdguard:
    enable: true #启用组件
    minLength: 4 #最小长度
    maxLength: 10 #最大长度
    characterExist: true #包含大小写字母
    numberExist: true #包含数字
    symbolExist: true #包含符号
    keyboardNotExist: true #不包含键盘排序
    allSameNotExist: true #不包含相同字符(数字或字母,如111或aaa)
    allSameNum: 3 #相同字符最大个数
    seqSameNotExist: true #不包含连续字符(数字或字母,如123或abc)
    seqSameNum: 3  #连续字符最大个数
    

在业务模块可以直接使用密码复杂度检测服务PasswdService,示例如下:

//注入服务
@Autowired
private PasswdService passwdService;
//调用服务方法
passwdService.checkPasswd(passwd)

# 3.3 SQL&XSS注入检测

配置参数如下

shanhai:
  webguard:
    enable: true #启用组件
    path-patterns: #检测范围
      - '/*'

# 3.4 Mysql数据安全检测

配置参数如下

shanhai:
  mysqlguard:
    enable: true       #启用组件
    where-exist: false  #包含where语句
    limit-exist: false  #包含limit语句
    query-limit: 20000  #limit条数

# 3.5 @RequestBody 通用解码组件

# 3.5.1 全量请求解码

配置参数如下

shanhai:
  decodebody:
    enable: true #启用组件

注:此组件只是做了通用性封装,对于解码逻辑需要自行实现接口DecodeBodyService的decodeRequestBody方法,在进行数据解码时,会自动调用该接口的实现方法进行自定义参数解码操作。

接口定义如下:

public interface DecodeBodyService {
    /**
     * 解析加密参数
     * @param body 原始报文数据
     * @return
     */
    public String decodeRequestBody(String body);
}

如果需要某个请求不进行解码操作,可以使用注解@DecodeBodyIgnore跳过对目标请求的解码操作。

# 3.5.2 指定请求解码

配置参数如下

shanhai:
  decodebody:
    enable: true #启用组件
    mode: 2      #指定请求解码模式,默认为全量解码 mode:1

使用示例如下

@DecodeBody(ruleId = "demio")
@ResponseBody
public String decodeOne(@RequestBody String body){
    return body;
}

注:此组件只是做了通用性封装,对于解码逻辑需要自行实现接口DecodeBodyService的decodeRequestBody方法,在进行数据解码时,会自动调用该接口的实现方法进行自定义参数解码操作。

接口定义如下:

public interface DecodeBodyService {
    default String decodeRequestBody(String ruleId,String body)
}

用户根据ruleId进行个性化解码组件开发。

# 3.6 SpringBoot配置文件参数加密

# 3.6.1 使用内置算法处理加密参数

内置解密算法(PBE)配置样例-(老版本实现,不再推荐使用)

shanhai:
  envdecode:
    market:
      algorithm: PBE           #算法名称
      pebPasswd: '20220111'    #密钥
      pebSalt: 'XCWT61iHdbg='  #盐值

内置解密算法(PBE安全模式)配置样例

shanhai:
  envdecode:
    market:
      algorithm: PBESafe           #算法名称
      pebPasswd: '20220111'    #密钥
      pebSalt: 'XCWT61iHdbg='  #盐值
      cycleNum: 100000         #循环次数(必须大于10000)

内置解密算法(AES)配置样例

shanhai:
  envdecode:
    market:
      algorithm: AES           #算法名称
      key: 'woniucsdnvip8888'  #密钥 (16字节)
      iv: 'bbCGNLlryzHaCL3P'   #向量 (16字节)
      mode: 1                  #模式 1:CBC 2:CFB

SpringBoot配置参数加密说明:

1)所有加密参数的Value值以**envdecode:😗*打头,举例如下:

app:
  version: 'envdecode::iezthxHWDp/fhXYXZSjhVw=='

2)加密结果生成样例

PBE加密算法

public static void main(String[] args) throws Exception {
    //生成盐值
    String salt= PBEUtils.initSalt();
    //自定义密钥
    String passwd="20220111";
    //待加密参数值
    String val="zhangsanxxx1";
    String encryptStr=PBEUtils.encrypt(val,passwd,salt);
    //加密结果
    System.out.println(salt+":"+encryptStr);
    //解密结果
    System.out.println(PBEUtils.decrypt(encryptStr,passwd,salt));
}

PBE安全模式加密算法

public static void main(String[] args) throws Exception {
    //生成盐值
    String salt= PBEUtils.initSalt();
    //自定义密钥
    String passwd="20220111";
    //待加密参数值
    String val="zhangsanxxx1";
    String encryptStr=PBEUtils.encryptSafe(val,passwd,salt,100000);
    //加密结果
    System.out.println(salt+":"+encryptStr);
    //解密结果
    System.out.println(PBEUtils.decryptSafe(encryptStr,passwd,salt,100000));
}

AES加密算法

public static void main(String[] args) {
        String text = "嗨,您好!";
        String key  = "woniucsdnvip8888";
        String iv  = getIV();
  
        String encryptTextCBC = encrypt(text, key, iv, AES_CBC);
        System.out.println("CBC 加密IV:" + iv);
        System.out.println("CBC 加密后内容:" + encryptTextCBC);
        System.out.println("CBC 解密后内容:" + decrypt(encryptTextCBC, key, iv, AES_CBC));
        System.out.println();

        String encryptTextCFB = encrypt(text, key, iv, AES_CFB);
        System.out.println("CFB 加密IV:" + iv);
        System.out.println("CFB 加密后内容:" + encryptTextCFB);
        System.out.println("CFB 解密后内容:" + decrypt(encryptTextCFB, key, iv, AES_CFB));

    }

# 3.6.2 自定义处理加密参数

首先需要实现一个自己的参数解密类,要求该类必须继承于PropertyDecode,并实现其定义的解密方法getProperty,PropertyDecoded定义如下:

public  abstract class PropertyDecode {
    /**
     * 自定义解析算法
     * @param envProperties 通用配置参数 
     * @param configProperties 待解密属性key:value
     * @return
     */
    @Nullable
    public abstract Properties getProperty(Properties envProperties,Properties configProperties);
}

注:SpringBoot配置文件中shanhai.envdecode打头的参数都会传递到envProperties中,可以自己获取自己配置的自定义参数。

在SpringBoot的配置文件中指定自己新增的自定义解密类,如下所示:

shanhai:
 envdecode:
    # 自定义解密类的优先级最高
    className: 'com.xxx.RSAPropertyDecode'

注 :如果既存在自定义解密类,又存在PBE解密配置的,自定义解密类优先级最高。

# 3.7 基于Mybatis-Plus进行字段级加解密与数据脱敏

数据处理 生效模式 备注
数据加密 新增|修改|查询 不支持 QueryWrapper进行查询
数据解密 查询
数据脱敏 新增|修改|查询 当执行新增|修改操作时,脱敏与加密不建议同时使用

数据生效范围

范围定义 作用域 备注
DataExecModel.INSERT 数据新增操作
DataExecModel.UPDATE 数据更新操作
DataExecModel.SELECT 数据查询操作
DataExecModel.INSERTANDUPDATE 数据新增或更新操作
DataExecModel.ALL 数据新增|修改|查询操作

# 3.7.1 使用内置算法进行数据字段级加解密操作

配置参数如下:

shanhai:
  dataguard:
    enable: true       #启用组件
    trace-log: true    #启用跟踪日志
    encryptRulesExt:  #加密算法参数配置(对称加密&非对称加密均需配置)
      - {ruleId: 'AES', ruleParams: {key: wjy59188wjy59188}} #内置AES加密算法示例(key的长度为16位) 
      - {ruleId: 'SM4', ruleParams: {key: wjy59188wjy59188}} #内置SM4加密算法示例(key的长度为16位) 
      - {ruleId: 'HMACSHA256', ruleParams: {key: wjy59188wjy59188}} #内置HMACSHA256加密算法示例
      - {ruleId: 'RSA', ruleParams: {publicKey: wjy59188wjy59188,privateKey: wjy59188wjy59188}} #内置RSA加密算法示例
      - {ruleId: 'SM2', ruleParams: {publicKey: wjy59188wjy59188,privateKey: wjy59188wjy59188}} #内置SM2加密算法示例 

注:此处的ruleId的值是示例,实际使用时需要修改为FieldDataGuard中定义的ruleId对应的值,ruleId必须自行确保全局唯一,否则数据解析会有问题。ruleParams中的参数,使用内置算法时,参数名必须为示例配置中的参数名才可以,相关key的值可以自己自定义。

如果需要使用国密算法,需要引入额外的依赖才可以

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>${bcprov.version}</version>
        </dependency>

在Domain中添加类扫描注解@ShanHaiDataGuard,在需要操作的字段添加 @FieldDataGuard注解:

加密示例如下:

@ShanHaiDataGuard
public class DictData implements Serializable {
    private static final long serialVersionUID = 1L;
    @FieldDataGuard(ruleId = "dictLabel",encrypt = true,encryptMethod = DataEncryptDef.SHA256)
    private String dictLabel;
}

解密示例如下:

@ShanHaiDataGuard
public class DictData implements Serializable {
    private static final long serialVersionUID = 1L;
    @FieldDataGuard(ruleId = "dictLabel",decrypt  = true,decryptMethod = DataEncryptDef.AES,decryptExecModel = DataExecModel.SELECT)
    private String dictLabel;
}

@FieldDataGuard 各字段定义

字段定义 字段类型 字段说明
ruleId String 规则ID,用于自行根据规则进行相关扩展
encrypt boolean 是否启用数据加密
encryptMethod String 加密算法 默认算法集参考DataEncryptDef
encryptExecModel String 执行加密算法的时机 参考DataExecModel
decrypt boolean 是否启用数据查询解密
decryptMethod String 解密算法 默认算法集参考DataEncryptDef
hyposensit boolean 是否启用数据脱敏
hyposensitMethod String 数据脱敏算法 默认算法集参考DataHyposensitDef
hyposensitExecModel String 执行脱敏算法的时机 参考DataExecModel

ShanHaiTmpData可以获取到原始字段值以及处理这个值所需要的运行算法名称。需要注意的是,如果是既要加密又要脱敏,则先执行脱敏再执行加密。如果既要解密又要脱敏,则先执行解密然后执行脱敏。

加解密默认算法(DataEncryptDef):

MD5/SHA256/HMACSHA256/RSA/AES/SM2/SM3/SM4

# 3.7.2 数据字段级脱敏操作

配置参数如下:

shanhai:
  dataguard:
    enable: true       #启用组件
    trace-log: true    #启用跟踪日志
    hyposensitRulesExt: #脱敏规则定义(需要自行扩展时才需要配置,默认不需要配置该扩展)
      - {ruleId: 'yourRule',regex: '([1][1-9]\d{1})\d{4}(\d{4})',replacement: '$1****$2'}

注:此处的ruleId的值是示例,实际使用时自己可以改为其他标识字符串即可,regex为自定义正则,replacement为脱敏后的格式。使用的String.replaceAll做脱敏处理。

脱敏默认算法(DataHyposensitDef):

IDcard/RealName/TelPhone/email/money 分别对应的是:身份证/姓名/手机号/邮箱/金额

数据脱敏示例如下:

@ShanHaiDataGuard
public class DictData implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @FieldDataGuard(ruleId = "dictLabel",hyposensit = true,hyposensitMethod = DataHyposensitDef.IDcard,hyposensitExecModel = DataExecModel.QUERY)
    private String dictLabel;
}

# 3.7.3 使用自定义算法进行数据字段级加解密或脱敏操作

配置参数如下:

shanhai:
  dataguard:
    enable: true       #启用组件
    encryptRulesExt:  #加密算法参数配置(自定义算法实现)
      - {ruleId: 'xxxxx', ruleParams: {key: wjy59188wjy59188, iv: aaaaa1111, source: device}} 

此处ruleParams内的参数你可以按照自己需要进行自定义,此处只是一个示例。

在自己的数据解析实现扩展加解密算法或者数据脱敏操作。

@Service
public class XXXDataGuardServiceImpl extends DefaultDataGuardServiceImpl {
    @Autowired
    private DataGuardConfig shanhaiDataGuardConfig;

    public XXXDataGuardServiceImpl(DataGuardConfig shanhaiDataGuardConfig) {
        super(shanhaiDataGuardConfig);
    }

    @Override
    public String encrypt(ShanHaiTmpData shanHaiTmpData) {
        //TODO 可以在此处扩展自定义加密算法实现
        return super.encrypt(shanHaiTmpData);
    }

    @Override
    public String decrypt(ShanHaiTmpData shanHaiTmpData) {
        //TODO 可以在此处扩展自定义解密算法实现
        return super.decrypt(shanHaiTmpData);
    }
}

自己定义的加解密算法的配置参数可以从DataGuardConfig->encryptRulesExt->EncryptRule中获取,ruleParams在EncryptRule中以Map的形式存在。对于Domain对象中的ruleId以及原始字段数据可以从ShanHaiTmpData中获取。

对于脱敏,暂不支持配置自定义脱敏算法。需要实现的可以自己重写hyposensit的实现。

# 3.8基于序列化的响应报文数据脱敏

配置参数如下:

shanhai:
  respguard:
    enable: true       #启用组件
    trace-log: true    #启用跟踪日志

# 3.8.1 基于规则ID定制字段级脱敏规则

在响应报文VO对象中需要进行脱敏的字段,添加注解 @RespFieldGuard并增加规则定义:

public class RespInfo {
    @RespFieldGuard(ruleId = "text")
    private String text;
}

实现RespGuardRuleDefService接口,实现自己的脱敏算法集合:

@Service
public class OpenRespGuardRuleDefService implements RespGuardRuleDefService {
    @Override
    public Object jsonGenerator(String ruleId, Object fieldValue) {
        return String.valueOf(fieldValue)+"@"+ruleId;
    }
}

注:此处只是做了通用性封装,没有内置任何脱敏算法,相关规则需要自己来实现。

# 3.8.2 基于目标类名和字段名实现全字段动态控制

在响应报文VO对象上添加注解 @RespDataGuard:

@RespDataGuard
public class RespInfo {
    private String text;
}

实现RespGuardRuleDefService接口,实现自己的脱敏算法集合:

@Service
public class OpenRespGuardRuleDefService implements RespGuardRuleDefService {
    @Override
    public Object jsonDynamicGenerator(String tragetClass, Object tragetField, Object fieldValue) {
        if(tragetField.equals("getMsg")){
            return String.valueOf(fieldValue)+"@"+tragetClass+":"+tragetField;
        }
        //注意:对于不需要进行特殊处理的数据,需要在此返回正常对象值
        return fieldValue;
    }
}

注:此处只是做了通用性封装,没有内置任何脱敏算法,相关规则需要自己来实现。

RespDataGuard与RespFieldGuard两种不能同时存在,RespDataGuard优先级高,会优先进行序列化处理。

# 3.9通用响应报文编码组件

# 3.5.1 全量请求解码

配置参数如下

shanhai:
  encodebody:
    enable: true #启用组件

注:此组件只是做了通用性封装,对于编码逻辑需要自行实现接口EncodeBodyService的encodeRespBody方法,在进行数据编码时,会自动调用该接口的实现方法进行自定义参数编码操作。

接口定义如下:

    /**
     * 加密响应
     * @param body 原始响应报文
     * @return
     */
    default String encodeRespBody(String body){
        return body;
    };

如果需要某个请求不进行解码操作,可以使用注解@EncodeBodyIgnore跳过对目标请求的解码操作。

# 3.5.2 指定请求解码

配置参数如下

shanhai:
  encodebody:
    enable: true #启用组件
    mode: 2      #指定请求加密模式,默认为全量加密 mode:1

使用示例如下

@EncodeBody(ruleId = "demo")
public JSONObject encode(){
  return "demoxx";
}

注:此组件只是做了通用性封装,对于编码逻辑需要自行实现接口EncodeBodyService的encodeRespBody方法,在进行数据编码时,会自动调用该接口的实现方法进行自定义参数编码操作。

接口定义如下:

     /**
     * 按照自定义规则ID加密响应报文
     * @param ruleId 规则ID
     * @param body 原始报文
     * @return
     */
    default String encodeRespBody(String ruleId,String body)

用户根据ruleId进行个性化编码组件开发。

# 3.10 敏感词检测过滤组件

配置参数如下

shanhai:
  sensitivewords:
    enable: true
    sensitive-filter-mode: 1 #敏感词过滤模式 (1:自动脱敏放行 2:拒绝执行)
    path-patterns: #敏感词检测范围
      - '/*'
    sensitive-words: "今天, 今天很好, 今天真烦" #敏感词清单(多个以英文,分隔)
    task-enable: true #是否启用定时更新敏感词词库任务
    task-inteval-period: 60 #定时更新频率 (单位:秒,默认值:600s)

山海Guard敏感词检测组件使用DFA算法对敏感词进行检测,支持通过配置文件进行敏感词配置,同时支持定时更新敏感词词库配置能力。

如果启用定时更新敏感词词库任务,则需要自行实现如下接口,动态加载敏感词数据:

public interface SensitiveWordsDictService {
    public Set<String> loadDict();
}

# 3.11 常见问题

# 3.11.1 文件上传检测不生效

在springboot中使用多个继承WebMvcConfigurationSupport的类是行不通的,而且使用注解@configuration去加载配置类只能挂载一个继承WebMvcConfigurationSupport。

解决办法:自己项目中的webmvc配置要实现 webMvcConfigurer 接口而不能使用继承WebMvcConfigurationSupport类的方式,这样组件和自己项目中的webmvc都可以挂载。

示例如下:

@Configuration
public class XXXConfig implements WebMvcConfigurer {

}