安全规范代码示例(请求及响应加解密、接口请求验证签名、入参过滤器、异常referer请求/登录拦截器)

一:加解密入参/出参
目的:为了防止请求参数及响应数据被暴力破解或截取,通过AES对称性加密传输规范,对请求及响应进行加解密处理

实现过程:创建请求及响应拦截器,对请求及响应源头进行数据处理,保证数据的安全传输

DecryptRequestBodyAdvice 客户端请求拦截器
EncodeResponseBodyAdvice 客户端响应拦截器
请求拦截器代码片段

@Component
如果放开注释,则表示监控所有controller请求入口
//@ControllerAdvice(basePackages = “com.ds.tech.controller”)
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

@Autowired
SystemConfig systemConfig;

/**
 * 加载配置文件信息
 */
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) {
    return true;
}

/**
 *读取请求头之前
 */
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
    return inputMessage;
}


@Override
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) {
    Object dealData = null;
    LogWriter.writeWorkLog("执行对客户端请求的数据解密");
    try {
        Map<String, String> dataMap = (Map) body;
        if (!MapUtils.isEmpty(dataMap)) {
            String srcData = dataMap.get("encryptStr");
            dealData = AESUtils.encrypt(srcData, systemConfig.getEncryptKey());
            LogWriter.writeWorkLog("解密后的数据:" + dealData);
        }
    } catch (Exception e) {
        LogWriter.writeErrorLog("解密失败", e);
    }
    return dealData;
}

@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
        Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    return body;
}

响应拦截器代码片段

/**

  • @title 客户端响应拦截器

  • @author chenpengfei

  • @date 2020年04月14日

  • @param body

  • @return object
    */
    @Component
    //@ControllerAdvice(“com.ds.tech.controller”) 放开注释则监听所有响应
    @SuppressWarnings(value = { “all” }) 剔除所有警告注解
    public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {
    @Autowired
    SystemConfig systemConfig;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    return true;
    }

    /**

    • @title 加密响应数据
    • @author chenpengfei
    • @date 2020年04月14日
    • @param body:最终响应的内容
    • @return object
      */
      @Override
      public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
      Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
      // 禁止fastjson转换循环引用
      String bodyStr = JSON.toJSONString(body, SerializerFeature.DisableCircularReferenceDetect);
      LogWriter.writeWorkLog(“开始加密服务器相应的数据:{}” + JSON.toJSONString(body));
      ResultObject resultObject = new ResultObject();
      String encodeStr = null;
      JSONObject jsonData = JSONObject.parseObject(bodyStr);
      try {
      if (!ObjectUtils.isEmpty(jsonData)) {
      encodeStr = AESUtils.encrypt(jsonData.getString(“data”).toString(),systemConfig.getEncryptKey());
      LogWriter.writeWorkLog(“数据加密完成:{}” + encodeStr);
      }
      } catch (Exception e) {
      LogWriter.writeErrorLog(AesEnum.DE_DATA_ERROR.getMessage(), e);
      resultObject.setFailed(AesEnum.DE_DATA_ERROR);
      return resultObject;
      }
      // 赋值返回值
      OptASRData resultData = new OptASRData();
      resultData.setResultEnData(encodeStr);
      resultObject.setRetcode((int) jsonData.get(“retcode”));
      resultObject.setSucceed(resultData);
      resultObject.setRetmsg(jsonData.getString(“retmsg”));
      return resultObject;
      }

}

aes加解密方法说明

srcData:为接口请求传输的加密后的数据
encryptKey:为与接口调用者约束的对称性秘钥
AESUtils.encrypt(srcData, systemConfig.getEncryptKey());
加密及解密工具类代码

private static final String KEY_AES = “AES”;

public static String encrypt(String src, String key) throws Exception {
    byte[] raw = key.getBytes();
    SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_AES);
    Cipher cipher = Cipher.getInstance(KEY_AES);
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    byte[] encrypted = cipher.doFinal(src.getBytes());
    return byte2hex(encrypted);
}

public static String decrypt(String src, String key) throws Exception {
    byte[] raw = key.getBytes();
    SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_AES);
    Cipher cipher = Cipher.getInstance(KEY_AES);
    cipher.init(Cipher.DECRYPT_MODE, skeySpec);
    byte[] encrypted1 = hex2byte(src);
    byte[] original = cipher.doFinal(encrypted1);
    String originalString = new String(original);
    return originalString;
}

private static byte[] hex2byte(String strhex) {
if (strhex == null) {
return null;
}
int l = strhex.length();
if (l % 2 == 1) {
return null;
}
byte[] b = new byte[l / 2];
for (int i = 0; i != l / 2; i++) {
b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
}
return b;
}

private static String byte2hex(byte[] b) {
    StringBuilder hs = new StringBuilder();
    String stmp = "";
    for (int n = 0; n < b.length; n++) {
        stmp = (Integer.toHexString(b[n] & 0XFF));
        if (stmp.length() == 1) {
            hs.append("0").append(stmp);
        } else {
            hs.append(stmp);
        }
    }
    return hs.toString().toUpperCase();
}
    (接口请求及加密规则见附件大神接口服务验签及加密规则)

二:接口请求验证签名
目的:为了接口请求的安全性,对接口调用者和服务提供者追加延签约束

实现过程:

1.接口调用者通过指定提供的接口服务号,用户登录生成的非固定token权限,服务请求的对称性秘钥key生成base64sign码做为接口请求的参数传递

2.服务提供者通过接口调用者传递的参数进行拼接,通过服务器端生成sign并与入参携带的sign进行对比

aop验签切面方法说明

/**
* 定义Controller方法切入点
/
@Pointcut("execution(
com.ds.tech.controller…*(…))")
public void controllerMethodPointcutApi() {
}

@Pointcut("controllerMethodPointcutApi()")
public void controllerMethodPointcut() {
}

/**
*
* @param pjp
* @return
*/
@Around(“controllerMethodPointcut()”)
public Object doAround(ProceedingJoinPoint pjp) {

    Object result = null;
    ResultObject<JSONObject> resultObject = new ResultObject<>();

    try {
        resultObject.setData(new JSONObject());
        Object[] args = pjp.getArgs();

        // 如果参数存在则进行api签名验证
        if (args.length <= 0) {
            resultObject.setFailed(ApiCommonEnum.INVALID_PARAMS.getCode(),
                    ApiCommonEnum.INVALID_PARAMS.getMessage());
            result = resultObject;
            return result;
        }

        Object iptObj = args[0];
        String dtoClassName = iptObj.getClass().getName();
       
            ComIobj<?> comIptObj = getComObject(iptObj);
            resultObject = comSign(comIptObj);
     

        if (resultObject.getRetcode() != NumberConst.INT_ZERO) {
            return resultObject;
        }
    } catch (Exception e) {

        LogWriter.writeErrorLog(e);
        resultObject.setFailed(ApiCommonEnum.SIGN_EXP.getCode(), ApiCommonEnum.SIGN_EXP.getMessage());
        result = resultObject;
        return result;
    }

    try {
        result = pjp.proceed();
    } catch (Throwable e) {

        LogWriter.writeErrorLog(e);

        resultObject.setFailed(ApiCommonEnum.SIGN_EXP.getCode(), ApiCommonEnum.SIGN_EXP.getMessage());
        result = resultObject;
        return result;

    }

    return result;
}

三:入参过滤器
目的:为了防止服务端XML分析器没有进行适当的数据验证,攻击者可以通过设计特殊的输入,破坏应用程序的运行并执行某些未授权的操作带来的风险,固对请求参数进行过滤转译

实现过程:再验签方法后追加过滤代码片段

处理入参过程

try {
        result = pjp.proceed(); // 将try catch 内代码替换如下
    } catch (Throwable e) {


        // 环绕切换方法执行后置任务
        Object[] args = pjp.getArgs();
        // 获取执行方法的对象
        Object iptObj = args[0];
        // 对象转换大神入参包装类
        ClientIobj<?> clientIptObj = getClientObject(iptObj);
        // 从包装类获取实际请求参数对象
        Object data = clientIptObj.getData();
        // 将入参转json数据
        String json = JSON.toJSONString(data);
        // 参数转数组(为了取每一段参数)
        String[] strArray = json.split(",");
        // 执行过滤方法将配置需要过滤的参数进行处理
        String parameterValues = getParameterValues(strArray);
        // 将转换过后的数据赋值给原始入参对象
        Object resultData = JSON.parseObject(parameterValues, clientIptObj.getData().getClass());
        // 利用反射给对象重新赋值
        Method setReadOnly = clientIptObj.getClass().getMethod("setData", Object.class);
        // 定义需要赋值的对象
        Object s = resultData;
        // 赋值过程
        setReadOnly.invoke(clientIptObj, s);
        // 重定义执行方法的对象
        args[0] = clientIptObj;
        // 执行后置任务
        Object ret = pjp.proceed(args);
        // 转发result请求
        result = ret;
  
public String getParameterValues(String[] parameter) {
    // 获取需要进行过滤的入参数量
    int count = parameter.length;
    // 初始化返回入参字符串
    String str = null;
    for (int i = 0; i < count; i++) {
        if (!StringUtils.isEmpty(str)) {
            // 拼接过滤后的入参
            str += "," + cleanXSS(parameter[i]);
        } else {
            // 赋值过滤的入参
            str = cleanXSS(parameter[i]);
        }
    }
    // 返回拼接且过滤后的入参
    return str;
} 

private static String cleanXSS(String value) {
    value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
    value = value.replaceAll("%3C", "&lt;").replaceAll("%3E", "&gt;");
    value = value.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");
    value = value.replaceAll("%28", "&#40;").replaceAll("%29", "&#41;");
    value = value.replaceAll("'", "&#39;");
    value = value.replaceAll("eval\\((.*)\\)", "");
    value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
    value = value.replaceAll("script", "");
    value = value.replaceAll("onclick", "");
    value = value.replaceAll("alert", "");
    return value;
}

(随带一笔)AOP、过滤器、拦截器执行顺序

过滤器 > 拦截器 > AOP切面 (执行顺序的规则不做描述)

为了既要满足aop的验签,又要满足过滤器的特殊字符过滤,通过过滤器和aop是不可能执行的,所以将过滤器的方法放在验签执行后

四:异常referer请求/登录拦截器
目的:

referer拦截器的作用:攻击者可以构造仅高权限用户才能够进行操作的请求,诱使登录状态的高权限用户点击伪造的链接、图片,从而成功执行非法的请求,以用来执行重要的查看,修改,删除,授权等恶意操作,一般用来防止盗链。
登录拦截器的约束:验证用户登录的token时效性,服务端会对请求的用户信息进行校验,满足时效条件放行否则踢出登录。
实现过程:追加登录referer拦截器

referer拦截器过程

创建 LoginInterceptor 类,继承HandlerInterceptorAdapter
@Service
public class LoginInterceptor extends HandlerInterceptorAdapter {

/**
* 白名单
/
private String[] refererDomain = new String[] { “www.baidu.com”, “xxx.xxx.xx” };
/
*
* 是否开启referer校验
*/
private Boolean check = true;

/**
* @title:拦截器开始
* @author:chenpengfei
* @date:2020-04-28
* @param:request
* @param:response
* @param:handler
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {

    if (check) {
        // 校验请求referer
        if (!checkReferer(request, response)) {
            returnErrorMessage(response, BaseEnum.REFERER_ERROR.getCode(), BaseEnum.REFERER_ERROR.getMessage());
        }
    }

    // 校验用户cookie
    OptCmsLoginUser loginUser = cmsUserService.getLoginUser();
    if (ObjectUtils.isEmpty(loginUser)) {
        logout(request, response);
        returnErrorMessage(response, BaseEnum.COOKIE_USER_NULL.getCode(), BaseEnum.COOKIE_USER_NULL.getMessage());
        return false;
    }

    LogWriter.writeWorkLog("验证用户token有效性 验证的数据:" + loginUser.toString());
    // 验证token
    if (!cmsUserService.tokenCheck(loginUser, request)) {
        logout(request, response);
        returnErrorMessage(response, BaseEnum.TOKEN_OUT_TIME.getCode(), BaseEnum.TOKEN_OUT_TIME.getMessage());
        return false;
    }
    return super.preHandle(request, response, handler);
}

private void logout(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
    try {
        LoginCookieHandle.logout(response);
    } catch (Exception e) {
        LogWriter.writeErrorLog("登录拦截logout异常", e);
    }
}

private void returnErrorMessage(HttpServletResponse response, Integer code, String errorMessage)
        throws IOException {

    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json; charset=utf-8");
    JSONObject res = new JSONObject();
    res.put("retcode", code);
    res.put("retmsg", errorMessage);
    PrintWriter out = null;
    out = response.getWriter();
    out.write(res.toString());
    LogWriter.writeWorkLog("拦截异常成功返回错误信息  msg:" + res.toString());
    out.flush();
    out.close();
}

private boolean checkReferer(HttpServletRequest request, HttpServletResponse response) {
    String referer = request.getHeader("referer");
    String host = request.getServerName();
    LogWriter.writeWorkLog("referer请求拦截执行:host=" + host + ";" + "referer=" + referer);
    // 验证非get请求
    if (!"GET".equals(request.getMethod())) {
        if (referer == null) {
            // 状态置为404
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return false;
        }
        java.net.URL url = null;
        try {
            url = new java.net.URL(referer);
        } catch (MalformedURLException e) {
            LogWriter.writeErrorLog(BaseEnum.REFERER_ERROR.getMessage(), e);
            // URL解析异常,也置为404
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return false;
        }
        // 首先判断请求域名和referer域名是否相同
        if (!host.equals(url.getHost())) {
            // 如果不等,判断是否在白名单中
            if (refererDomain != null) {
                for (String s : refererDomain) {
                    if (s.equals(url.getHost())) {
                        return check;
                    }
                }
            }
            return false;
        }
    }
    return check;
}

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值