项目开发中,我们不可避免的需要对外暴露接口,尤其是APP。往往我们需要对APP进行最基础的权限认证,防止接口越权等问题。然而最近有用户给我们提交了一个BUG, 说利用此BUG可以获取 所有用户 信息,一听到这个差点吓死,还好不是我们开发的项目,不过今天我们还是来复盘一下。
- 1.这个漏洞是怎么出现的呢?
答:开放的接口中,直接上传用户id,然后后端代码根据上传的id查询用户信息并返回给前端,类似于这么一个接口/user/info?userId=1
,前端登录后获取token,然后用postman直接调用接口,随便传任何用户id,都会返回用户id对应的用户信息。然鹅最要命的是这个用户id还是自增的,最要命的是这个接口返回的信息还是包含手机号、身份证信息等敏感信息的,最要命的是它返回的信息还是没有脱敏的。 - 2.如何避免这个漏洞?
答:通过上面的解答,大家肯定已经想到了解决思路,第一就是当前用户只能访问当前用户的信息,且用户id不是直接由客户端传给服务端;第二就是用户id最好不好用自增主键;第三返回给前端的数据要做脱敏,简单的说比如你要展示手机号,后端给返回131**5037这种。 - 3.咱代码怎么写?
答:我们不直接用前端上传的用户id,而是用用户登录信息中的id,然后比如查询订单的时候,用用户id+订单号查询,避免前面说到的系统漏洞。我也在下面写了一段示例代码,给大家参考。
1.定义一个RequireLogin 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequireLogin {
}
2.定义一个AuthorizationInterceptor类实现HandlerInterceptor
@Component
public class AuthorizationInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtils jwtUtils;
public static final String USER_KEY = "userId";
public static final String STORE_ID = "storeId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
RequireLogin annotation;
if (handler instanceof HandlerMethod) {
annotation = ((HandlerMethod) handler).getMethodAnnotation(RequireLogin.class);
} else {
return true;
}
if (annotation == null) {
return true;
}
//获取用户凭证
String token = request.getHeader(jwtUtils.getHeader());
if (StringUtils.isBlank(token)) {
token = request.getParameter(jwtUtils.getHeader());
}
// 获取storeId
String storeId = request.getHeader(STORE_ID);
//凭证为空
if (StringUtils.isBlank(token)) {
throw new BaseException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());
}
if (StringUtils.isBlank(storeId)) {
throw new BaseException(storeId + "不能为空。");
}
Claims claims = jwtUtils.getClaimByToken(token);
if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
throw new BaseException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
}
//设置userId到request里,后续根据userId,获取用户信息
request.setAttribute(USER_KEY, Integer.parseInt(claims.getSubject()));
//设置storeId
request.setAttribute(STORE_ID, Integer.parseInt(storeId));
return true;
}
}
3.通过WebMvcConfig注册我们添加的拦截器、方法参数解析器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthorizationInterceptor authorizationInterceptor;
@Autowired
private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizationInterceptor).addPathPatterns("/app/**");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
}
}
4.controler添加注解实现需要登陆后访问
@ApiOperation("申请售后")
@RequireLogin
@PostMapping("apply")
public Result apply(@RequestAttribute("userId") Integer userId, @RequestAttribute("storeId") Integer storeId, @RequestBody CommonForm<RefundApplyForm> form) {
boolean apply = orderRefundService.apply(storeId, userId, form);
return Result.ok();
}
总结一下:系统的漏洞,往往都是前后端用户id这些传过来传过去,真正考虑安全的话,谢绝例如用户id直接从前端传。
评论 (0)