Java後端設計,如何避免用戶“合法”的冒名頂替他人和被他人頂替

某日編寫接口時,腦袋突然靈光一閃,意識到了一個可能會被很多碼農甚至程序設計者忽視掉的安全漏洞問題。

請先看下面的代碼,先上controller層代碼:

package csdn.controller;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/demo")
public class DemoController {

	private static final Logger logger = Logger.getLogger(DemoController.class);

	@Autowired
	private DemoService demoService;

	/**
	 * 用戶取消訂單接口
	 userId,下單用戶的id
    productId,商品id
	 num,商品數量
	 */
	@RequestMapping(value = "/order", method = RequestMethod.POST)
	@ResponseBody
	public Result order(@RequestParam("userId") String userId,Integer productId,Integer num) {
		Result result = new Result();

		try {

			result = demoService.order(userId,productId,num);

		} catch (Exception e) {
			result.setSuccess(false);
			result.setMsg("網絡異常:" + e.getMessage());
		}
		return result;
	}
	
}

 

然後是service 層的實現類:

package csdn.service.impl;

import org.apache.commons.lang.StringUtils;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DemoServiceImpl implements DemoService {

	private static final Logger logger = Logger.getLogger(DemoServiceImpl.class);

	@Override
	public Result order(String userId,Integer productId,Integer num) {

		Result result = new Result();
		
		if(StringUtils.isBlank(userId)||productId==null||num==null){
			result.setSuccess(false);
			result.setMsg("缺少必要參數");
			return result;
		}

		//核心代碼隱身符1,通過這個userId去網站數據庫查詢當前用戶是否存在
		//·······
		if(未註冊){
			result.setSuccess(false);
			result.setMsg("傳入userId錯誤,當前用戶不存在");
			return result;
		}
		
		//核心代碼隱身符2,通過這個productId去網站數據庫查詢產品是否存在
		//······
		if(不存在){
			result.setSuccess(false);
			result.setMsg("當前商品不存在");
			return result;
		}
		
		//核心業務隱身符,此處省略具體的操作
		//······

        result.setSuccess(true);
		return result;
	}
}

       省略Result.java實體類(你可以自己定義)和dao層的代碼,具體關注DemoServiceImpl類的order方法中的數據校驗邏輯,粗一看,好像並沒有什麼問題,再細一看,額,好像也沒什麼問題。可是真的沒什麼問題嗎?你有沒有發現,這個userId是前端傳來的,後端開發者一定要記住一條準則——“永遠不要相信前端傳來的數據”,因爲你永遠不會知道,你寫的接口,是通過正常渠道在被調用還是被僞造成正常情況來調用。因此,我們後端開發除了對必傳字段做非空校驗,還需要判斷一些傳入的數據是否合法,比如用戶的userId是否能在網站的數據庫裏查詢到,用戶傳來的訂單號是否真實存在等。

       回到前面的話題,爲什麼說前端傳來的這個userId是有問題的?我們分析之前有一個前提假設,用戶每次調用接口時需要攜帶一個token,只有token檢驗通過的時候,系統纔會放行並允許執行後面的操作。假設用戶張三的userId是【zhangsan666666】,他的token是【zhangsantoken】;用戶李四的userId是【lisi666666】,他的合法token是【lisitoken】。這時有一個訪問這個接口的請求,請求參數是:【token=zhangsantoken,userId=lisi666666,productId=120,num=1】,發現了嗎,張三用自己合法的token通過了校驗,但是卻以李四的名義下單並且下單成功了。

       對於前後端分離的項目,不妨這樣解決:用戶每次請求時攜帶一個token,同時不管需不需要,每個接口都要攜帶一個用戶的id(注意這個用戶id的參數名應該統一不變,比如統一定義爲:userId,一旦定義,後續的每個接口都要傳【userId=用戶的id】這對參數)。在用戶發送登錄請求時,後臺檢驗用戶名密碼校驗合法後,除了生成token外,還要以token爲key,用戶的userId爲value,保存鍵值對到redis中。以後用戶的每次請求,先校驗token,token校驗成功之後,再以token爲key去redis數據庫查詢對應的用戶id,拿這個用戶id與用戶傳來的userId進行比對,兩者一致才說明這個請求是合法的。

       而實際的開發中,其實還有很多類似的情形。比如你進行取消訂單的操作,首先是要用一條select語句去數據查詢這個訂單是否存在,同時校驗訂單狀態是否正常。此時這條select語句就有講究了,如果只是用一個orderId(即訂單號)作爲條件,顯然是不嚴謹的,此時至少還要加上用戶的userId這個條件,如果查詢到的記錄爲null,則說明這兩個參數一定有一個錯誤;如果返回了具體的記錄,說明這個訂單的確是屬於當前這個用戶的,他有權操作這個訂單。由此可以想象,那些支付和退款等更爲敏感的操作,後臺開發人員恐怕免不了要做出更爲嚴謹的校驗邏輯了。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章