1、越权分类
常见越权有水平越权、垂直越权和数据越权。
1.1 水平越权
用于A访问接口通过修改接口的参数操作了用户B的数据。比如查询订单列表接口,通过传递别人的userId查询别人的订单列表。这种问题出现的原因是接口直接使用用户传递的参数做租户隔离,并没有拿用户的参数与登录信息做对比,或者没有取登录信息里的用户信息做业务逻辑。解决办法:用户信息userInfo从登录信息里取。
1.2 垂直越权
用户A访问了超管接口/没有权限的接口。解决办法:做功能码鉴权,也就是做权限限制。可以为每个接口定义一个功能码,可在常量类里定义。然后在接口上添加自定义注解,把功能码添进去。然后用AOP做切面,在切面里读取登录用户的权限,查询权利里的功能码,看用户是否有当前接口权限。
1.3 数据越权
可以理解为水平越权的特例,接口使用了数据ID做参数。比如一个订单详情查询接口,入参是订单号,而订单号往往有规律,用户A在自己的一个合法的订单号基础上+1,就可能得到别人的一个存在的订单号,然后调用订单详情查询接口。
解决办法:查询接口默认添加一个用户信息做参数,用当前用户的登录信息做参数。或者先查询出来数据,在内存里对比用户信息。
2、其他安全措施
2.1 签名
与三方系统对接,无法通过登录信息做鉴权的,可以设计签名,预防接口被随意调用。解决方法:给三方系统颁发一个appId、Secret(记录在自己系统里),要求调用方传递参数时带上appId,然后传递一个签名sign,sign可以设计为 md5(业务参数+Secret)。接收方根据appId查询出来对应的Secret,然后用同样的算法计算sign,对比一致即可放行。设计签名,攻击方无法修改业务参数,因为修改了业务参数,sign便不对了,只要调用方不泄露secret,它就是安全的。接收方做好幂等,业务也不会有问题。最多被重放请求(ddos),并且重放也可以通过参数里加时间戳来解决。
2.2 数据脱敏
所有鉴权问题搞定后,数据字段透出最好做到最小化,前端不需要的字段不透出,敏感字段要脱敏。
3、代码实例
/**
* 获取订单详情
* @param orderNo
* @return
*/
@RequestMapping(value = "order/info",method = RequestMethod.POST)
@FunctionAuth(FunctionConstants.QUERY_ORDER)
public ResponseVo getOrder(@RequestParam("orderNo") String orderNo) {
// 基础非空校验
Assert.isTrue(!StringUtils.isEmpty(orderNo));
// 登录校验,从登录信息里获取用户信息
LogInUserInfo userInfo = LoginUtil.getUserInfo();
OrderInfo order = OrderService.getOrder(orderNo);
// 水平越权
if (order != null && order.getUserId() != userInfo.getId()) {
throw new BizException("非法订单查询");
}
// 脱敏处理
order = SensitiveUtil.del(order);
return new ResponseVo(order);
}