注解加拦截器实现自动加解密和数据完整性校验

在这里插入图片描述

概要

注解加拦截器实现自动加解密和数据完整性校验
一些敏感信息存入数据需要进行加密处理,比如电话号码,身份证号码等,从数据库取出到前端展示时需要解密
一些重要数据不可被篡改。

流程

SELECT时对添加了@SensitiveEntity注解的实体中添加了@SecurityField注解的字段type=CONFIDENTIALITY进行解密type=INTEGRITY进行完整性校验
UPDATE、INSERT时,对添加了@SensitiveEntity注解的实体中添加了@SecurityField注解的字段type=CONFIDENTIALITY进行加密type=INTEGRITY进行完整性保护

技术细节

实体注解@SensitiveEntity

/**
 * 敏感实体注解
 * 需要在实体类上添加
 * @author lq
 * @date 2024/07/30
 */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityEntity {
}

字段注解@SecurityField

/**
 * 敏感字段注解
 * 需要在字段上添加
 * @author lq
 * @date 2024/07/30
 */
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityField {
    SecurityType[] type() default SecurityType.CONFIDENTIALITY;
}

注解操作类型SecurityType

/**
 * 注解操作类型
 * INTEGRITY完整 CONFIDENTIALITY机密 SIGNATURE签名
 * @author lq
 * @date 2024/07/30
 **/
public enum SecurityType {
    INTEGRITY,
    CONFIDENTIALITY,
    SIGNATURE;

}

MyBatis拦截器

/**
 * 加解密、完整性拦截器
 *
 * @author lq
 * @date 2024/07/30
 **/
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
public class SecurityInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(SecurityInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        Object parameter = invocation.getArgs()[1];
        logger.info("------SecurityInterceptor.sqlCommandType------{}" + sqlCommandType);
        if (parameter == null) {
            return invocation.proceed();
        }
        //查询时,做完整性校验和解密
        if (SqlCommandType.SELECT == sqlCommandType) {
            //取出查询的结果
            Object returnValue = invocation.proceed();
            if (oConvertUtils.isEmpty(returnValue)) {
                return null;
            }
          	// TODO 这里returnValue如果是List或者Map,要特殊处理
            if (isSensitiveEntity(returnValue)) {
                try {
                	//验证完整性
                    validateEntityIntegrity(returnValue);
                } catch (Exception e) {
                    validateFail(returnValue);
                    return returnValue;
                }
                //如果完整性验证不通过,就没必要做解密操作了。
                try {
                    //解密
                    entityDecrypt(returnValue);
                }catch (Exception e){
                    logger.error("解密失败:{}",e);
                }
            }
            return returnValue;
        }
        //新增或修改时,做加密和完整性保护
        if (SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.INSERT == sqlCommandType) {
            if (parameter instanceof MapperMethod.ParamMap) {
                MapperMethod.ParamMap<?> p = (MapperMethod.ParamMap<?>) parameter;
                String et = "et";
                if (p.containsKey(et)) {
                    parameter = p.get(et);
                } else {
                    parameter = p.get("param1");
                }
                if (parameter == null) {
                    return invocation.proceed();
                }
            }
            if (isSensitiveEntity(parameter)) {
                //加密
                entityEncrypt(parameter);
                //完整性保护
                entityIntegrity(parameter);
            }
            return invocation.proceed();
        }
        return invocation.proceed();
    }


    /**
     * 完整性保护
     *
     * @param obj
     */
    private void entityIntegrity(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        List<String> list = new LinkedList<>();
        for (Field field : fields) {
            if (isSecurityField(field)) {
                String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
                field.setAccessible(true);
                Object value = field.get(obj);
                field.setAccessible(false);
                if (types.contains(SecurityType.INTEGRITY.toString())) {
                    if (oConvertUtils.isNotEmpty(value)) {
                        list.add(value.toString());
                    }
                }
            }
        }
        if (oConvertUtils.listIsNotEmpty(list)) {
            String hmacValue = getHmacValue(list);
            for (Field field : fields) {
                if (isSecurityField(field)) {
                    String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
                    if (types.contains(SecurityType.SIGNATURE.toString())) {
                        field.setAccessible(true);
                        field.set(obj, hmacValue);
                        field.setAccessible(false);
                    }
                }
            }
        }
    }

    /**
     * 验证完整性
     *
     * @param obj
     */
    private boolean validateEntityIntegrity(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        List<String> list = new LinkedList<>();
        String hmacValue = "";
        for (Field field : fields) {
            if (isSecurityField(field)) {
                String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
                field.setAccessible(true);
                Object value = field.get(obj);
                field.setAccessible(false);
                if (types.contains(SecurityType.INTEGRITY.toString())) {
                    if (oConvertUtils.isNotEmpty(value)) {
                        list.add(value.toString());
                    }
                }
                if (types.contains(SecurityType.SIGNATURE.toString())) {
                    if (oConvertUtils.isNotEmpty(value)) {
                        hmacValue = value.toString();
                    }
                }
            }
        }
        if (oConvertUtils.listIsEmpty(list) || oConvertUtils.isEmpty(hmacValue)) {
            //不进行完整性验证
            return false;
        }
        boolean isValidate = this.validate(list, hmacValue);
        return isValidate;
    }

    private boolean validate(List<String> list, String hmacValue) {
        String hmacValue2 = getHmacValue(list);
        if (hmacValue.equals(hmacValue2)) {
            return true;
        } else {
            throw new RuntimeException("数据完整性校验失败");
        }
    }

    /**
     * 对需要完整性保护的字段计算得到HmacValue
     *
     * @param list
     * @return {@link String }
     */
    private String getHmacValue(List<String> list) {
        String originalValue = StringUtils.join(list, "::");
        byte[] originalByte = originalValue.getBytes(StandardCharsets.UTF_8);
        //这里是我用的计算HAMC方法,可替换自己的
        SecretKey key = SymmetricOperationUtil.exportInternalKey(10);
        byte[] hmacByte = SymmetricOperationUtil.integrityHmac(key.getEncoded(), originalByte);
        String hmacValue2 = Base64.getEncoder().encodeToString(hmacByte);
        return hmacValue2;
    }

    /**
     * 完整性验证失败
     *
     * @param obj
     * @throws IllegalAccessException
     */
    private void validateFail(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (isSecurityField(field)) {
                String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
                if (types.contains(SecurityType.INTEGRITY.toString())) {
                    field.setAccessible(true);
                    //数据被篡改时,将数据改为"数据被篡改"作为提示。
                    field.set(obj, "数据被篡改");
                    field.setAccessible(false);
                }
            }
        }
    }

    /**
     * 加密
     *
     * @param obj
     */
    private void entityEncrypt(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (isSecurityField(field)) {
                String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
                if (types.contains(SecurityType.CONFIDENTIALITY.toString())) {
                    logger.info("类:{},加密字段:{}", obj.getClass(), field.getName());
                    field.setAccessible(true);
                    Object value = field.get(obj);
                    if (oConvertUtils.isNotEmpty(value)) {
                    	//这里是我用的加密操作,可以替换为自己的加密操作。
                        byte[] indata = value.toString().getBytes(StandardCharsets.UTF_8);
                        String encData = SymmetricOperationUtil.internalSm4Enc(1, "CBC", true, indata, new byte[16]);
                        field.set(obj, encData);
                    }
                    field.setAccessible(false);
                }
            }
        }
    }

    /**
     * 解密
     *
     * @param obj
     */
    private void entityDecrypt(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (isSecurityField(field)) {
                String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
                if (types.contains(SecurityType.CONFIDENTIALITY.toString())) {
                    field.setAccessible(true);
                    Object value = field.get(obj);
                    field.setAccessible(false);
                    if (oConvertUtils.isNotEmpty(value)) {
                        logger.info("{}:value:{}", field.getName(), value);
                        String decStr;
                        try {
                        	//这里是我用的解密操作,可以替换为自己的解密操作。
                            byte[] indata = Base64.getDecoder().decode(value.toString());
                            decStr = SymmetricOperationUtil.internalSm4Dec(1, "CBC", true, indata, new byte[16]);
                        }catch (Exception e){
                            logger.info("解密失败");
                            decStr = "解密失败";
                        }
                        field.setAccessible(true);
                        field.set(obj, decStr);
                        field.setAccessible(false);
                    }
                }
            }
        }
    }

    /**
     * 是否包含注解SensitiveEntity
     *
     * @param obj
     * @return boolean
     */
    private boolean isSensitiveEntity(Object obj) {
        if (oConvertUtils.isEmpty(obj)) {
            return false;
        }
        return obj.getClass().isAnnotationPresent(SecurityEntity.class);
    }

    /**
     * 是否包含注解SensitiveField
     *
     * @param field
     * @return boolean
     */
    private boolean isSecurityField(Field field) {
        if (oConvertUtils.isEmpty(field)) {
            return false;
        }
        return field.isAnnotationPresent(SecurityField.class);
    }
}

小结

只需要在对应的实体上增加相应的注解,即可实现

@SecurityEntity
public class AppUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * id
     */
    @TableId(type = IdType.ASSIGN_ID)
    @ApiModelProperty(value = "id")
    private String id;
    /**
     * 用户名
     */
    @Excel(name = "用户名", width = 15)
    @ApiModelProperty(value = "用户名")
    private String loginName;
    /**
     * 手机号
     */
    @Excel(name = "手机号", width = 15)
    @ApiModelProperty(value = "手机号")
    @SecurityField(type = {SecurityType.CONFIDENTIALITY, SecurityType.INTEGRITY})
    private String phoneNo;
    /**
     * 身份证号码
     */
    @Excel(name = "身份证号码", width = 15)
    @ApiModelProperty(value = "身份证号码")
    @SecurityField(type = {SecurityType.CONFIDENTIALITY, SecurityType.INTEGRITY})
    private String idCardNo;
    /**
     * HMAC值
     */
    @SecurityField(type = SecurityType.SIGNATURE)
    private String hmacValue;
}

新增编辑AppUser ,可以对相应的字段进行完整性保护和加密
查询AppUser 时会对相应字段进行完整性校验和解密。

不支持对缓冲数据进行操作

  • 19
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用Spring Boot注解拦截器实现权限认证的代码示例: 1. 创建自定义拦截器: ```java public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 在此处实现权限认证逻辑 String token = request.getHeader("Authorization"); if(token == null || token.isEmpty()) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 验证通过,放行 return true; } } ``` 2. 创建配置类: ```java @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private AuthInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/**") .excludePathPatterns("/login", "/register"); } } ``` 3. 在Controller类或方法上标注注解: ```java @RestController @RequestMapping("/api") public class MyController { // 标注注解,需要认证权限 @GetMapping("/userinfo") @AuthRequired public String getUserInfo() { // 实现获取用户信息逻辑 return "UserInfo"; } } ``` 4. 创建自定义注解: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthRequired { } ``` 5. 在拦截器中获取自定义注解: ```java @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取请求处理方法的所有注解 Method method = ((HandlerMethod)handler).getMethod(); Annotation[] annotations = method.getDeclaredAnnotations(); // 遍历注解数组,寻找 AuthRequired 注解 boolean authRequired = false; for(Annotation annotation : annotations) { if(annotation.annotationType() == AuthRequired.class) { authRequired = true; break; } } if(authRequired) { // 需要认证权限的逻辑处理 } // 验证通过,放行 return true; } ``` 以上就是使用Spring Boot注解拦截器实现权限认证的简单示例代码。请注意,在实际项目中,需要根据具体需求进行修改和完善。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值