项目开发中的安全问题怎么处理?

目录

1.客户的数据不可信

2. 客户端提交的参数需要校验

3.请求头里的内容出现错误


1.客户的数据不可信

@PostMapping("/order")
public void wrong(@RequestBody Order order) {
 this.createOrder(order);
}

 对应的实体类如下:

@Data
public class Order {
 private long itemId; //商品ID
 private BigDecimal itemPrice; //商品价格
 private int quantity; //商品数量
 private BigDecimal itemTotalPrice; //商品总价
}

这样的话,用户可能传过来的单价和总价有问题

服务端也一定要重新从数据库来初始化商品的价格,重新计算最终的订单价格。如果不这么做的话,很可能会被黑客利用,商品总价被恶意修改为比较低 的价格。

@PostMapping("/orderRight")
public void right(@RequestBody Order order) {
 //根据ID重新查询商品
 Item item = Db.getItem(order.getItemId());
 //客户端传入的和服务端查询到的商品单价不匹配的时候,给予友好提示
 if (!order.getItemPrice().equals(item.getItemPrice())) {
 throw new RuntimeException("您选购的商品价格有变化,请重新下单");
 }
 //重新设置商品单价
 order.setItemPrice(item.getItemPrice());
 //重新计算商品总价
 BigDecimal totalPrice = item.getItemPrice().multiply(BigDecimal.valueOf(order.getQuantity()));
 //客户端传入的和服务端查询到的商品总价不匹配的时候,给予友好提示
 if (order.getItemTotalPrice().compareTo(totalPrice)!=0) {
 throw new RuntimeException("您选购的商品总价有变化,请重新下单");
 }
 //重新设置商品总价
 order.setItemTotalPrice(totalPrice);
 createOrder(order);
}

        一种可行的做法是,让客户端仅传入需要的数据给服务端,像这样重新定义一个 POJO CreateOrderRequest 作为接口入参,比直接使用领域模型 Order 更合 理。在设计接口时,我们会思考哪些数据需要客户端提供,而不是把一个大而全的对象作为参数提供给服务端,以避免因为忘记在服务端重置客户端数据 而导致的安全问题。

推荐写法

@Data
public class CreateOrderRequest {
 private long itemId; //商品ID
 private int quantity; //商品数量
}
@PostMapping("orderRight2")
public Order right2(@RequestBody CreateOrderRequest createOrderRequest) {
 //商品ID和商品数量是可信的没问题,其他数据需要由服务端计算
 Item item = Db.getItem(createOrderRequest.getItemId());
 Order order = new Order();
 order.setItemPrice(item.getItemPrice());
 order.setItemTotalPrice(item.getItemPrice().multiply(BigDecimal.valueOf(order.getQuantity())));
 createOrder(order);
 return order;
}

2. 客户端提交的参数需要校验

@Slf4j
@RequestMapping("trustclientdata")
@Controller
public class TrustClientDataController {
 //所有支持的国家
 private HashMap<Integer, Country> allCountries = new HashMap<>();
 public TrustClientDataController() {
 allCountries.put(1, new Country(1, "China"));
 allCountries.put(2, new Country(2, "US"));
 allCountries.put(3, new Country(3, "UK"));
 allCountries.put(4, new Country(4, "Japan"));
 }
 @GetMapping("/")
 public String index(ModelMap modelMap) {
 List<Country> countries = new ArrayList<>();
 //从数据库查出ID<4的三个国家作为白名单在页面显示
 countries.addAll(allCountries.values().stream().filter(country -> country.getId()<4).collect(Collectors.toList()));
 modelMap.addAttribute("countries", countries);
 return "index";
 }
} 

 html写法

...
<form id="myForm" method="post" th:action="@{/trustclientdata/wrong}">
 <select id="countryId" name="countryId">
 <option value="0">Select country</option>
 <option th:each="country : ${countries}" th:text="${country.name}" th:value="${country.id}"></option>
 </select>
 <button th:text="Register" type="submit"/>
</form>
..

 以上的参数只有三个,假如有以下的方法:

@PostMapping("/wrong")

@ResponseBody

public String wrong(@RequestParam("countryId") int countryId) {

 return allCountries.get(countryId).getName();

}

如何我在访问的时候直接发送以下请求

curl http://localhost:45678/trustclientdata/wrong\?countryId=4 -X POST

那么肯定会系统报错

解决办法:

@PostMapping("/right")
@ResponseBody
public String right(@RequestParam("countryId") int countryId) {
 if (countryId < 1 || countryId > 3)
 throw new RuntimeException("非法参数");

 return allCountries.get(countryId).getName();

}

或者使用spring validation

@Validated

public class TrustClientParameterController {

 @PostMapping("/better")
 @ResponseBody
 public String better(

 @RequestParam("countryId")

 @Min(value = 1, message = "非法参数")

 @Max(value = 3, message = "非法参数") int countryId) {
 return allCountries.get(countryId).getName();
 }
}

客户端提交的参数需要校验的问题,可以引申出一个更容易忽略的点是,我们可能会把一些服务端的数据暂存在网页的隐藏域中,这样下次页面提交的时 候可以把相关数据再传给服务端。虽然用户通过网页界面的操作无法修改这些数据,但这些数据对于 HTTP 请求来说就是普通数据,完全可以随时修改为 任意值。所以,服务端在使用这些数据的时候,也同样要特别小心。

3.请求头里的内容出现错误

@Slf4j

@RequestMapping("trustclientip")

@RestController

public class TrustClientIpController {
 HashSet<String> activityLimit = new HashSet<>();
 @GetMapping("test")
 public String test(HttpServletRequest request) {
 String ip = getClientIp(request);
 if (activityLimit.contains(ip)) {
 return "您已经领取过奖品";
 } else {

 activityLimit.add(ip);

 return "奖品领取成功";
 }
 }
 private String getClientIp(HttpServletRequest request) {
 String xff = request.getHeader("X-Forwarded-For");
 if (xff == null) {
 return request.getRemoteAddr();
 } else {
 return xff.contains(",") ? xff.split(",")[0] : xff;

 }

 }

}

通过一个 HashSet 模拟已发放过奖品的 IP 名单,每次领取奖品后把 IP 地址加入这个名单中。IP 地址的获取方式是:优先通过 X-Forwarded-For 请求头来获 取,如果没有的话再通过 HttpServletRequest 的 getRemoteAddr 方法来获取。通常我们的应用之前都部署了反向代理或负载均衡器,remoteAddr 获得的只 能是代理的 IP 地址,而不是访问用户实际的 IP。这不符合我们的需求,因为反向代理在转发请求时,通常会把用户真实 IP 放入 X-Forwarded-For 这个请求 头中。这种过于依赖 X-Forwarded-For 请求头来判断用户唯一性的实现方式,是有问题的。

比如我们可以通过模拟发送下面的请求头信息来影响业务:

curl http://localhost:45678/trustclientip/test -H "X-Forwarded-For:183.84.18.71, 10.253.15.1"

因此,IP 地址或者说请求头里的任何信息,包括 Cookie 中的信息、Referer,只能用作参考,不能用作重要逻辑判断的依据。而对于类似这个案例唯一性的 判断需求,更好的做法是,让用户进行登录或三方授权登录(比如微信),拿到用户标识来做唯一性判断

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ADRU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值