前後端分離架構中的接口安全(下)

接著上一篇,我們繼續來討論。

輸入參數的合法性校驗

一般情況下,客戶端會進行參數的合法性校驗,這個只是為了減輕服務端的壓力,針對於普通用戶做的校驗,如果黑客通過直接調用接口地址,就可繞過客戶端的校驗,這時要求我們服務端也應該做同樣的校驗。

SpringMVC提供了專門用於校驗的註解,我們通過AOP即可實現統一的參數校驗,下面請看代碼:

<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-aop/<artifactid>
/<dependency>
@Aspect
@Component
public class WebExceptionAspect {
private static final Logger logger = LoggerFactory.getLogger(WebExceptionAspect.class); //凡是註解了RequestMapping的方法都被攔截
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") private void webPointcut() {
} /**
* 攔截web層異常,記錄異常日誌,並返回友好信息到前端 目前只攔截Exception,是否要攔截Error需再做考慮
*
* @param e
* 異常對象
*/
@AfterThrowing(pointcut = "webPointcut()", throwing = "e") public void handleThrowing(Exception e) {
e.printStackTrace();
logger.error("發現異常!" + e.getMessage());
logger.error(JSON.toJSONString(e.getStackTrace())); try { if(StringUtils.isEmpty(e.getMessage())){
writeContent(JSON.toJSONString(SingleResult.buildFailure()));
}else {
writeContent(JSON.toJSONString(SingleResult.buildFailure(Code.ERROR,e.getMessage())));
}

}catch (Exception ex){
ex.printStackTrace();
}
} /**
* 將內容輸出到瀏覽器
*
* @param content
* 輸出內容
*/
private void writeContent(String content)throws Exception {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getResponse();
response.reset();
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/plain;charset=UTF-8");
response.setHeader("icop-content-type", "exception");
response.getWriter().print(content);
response.getWriter().close();
}
}

在controller提供共有方法:

protected void validate(BindingResult result){

if(result.hasFieldErrors()){

List<fielderror> errorList = result.getFieldErrors();/<fielderror>

errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));

}

}

每個接口的輸入參數都需要加上@Valid註解,並且在參數後面加上BindResult類:

@RequestMapping(value = "/hello",method = RequestMethod.POST) 
public SingleResult<string> hello(@Valid @RequestBody TestRequest request, BindingResult result){
validate(result); r
eturn "name="+name;

public class TestRequest{
@NotNull(message = "name不能為空") private String name; public String getName() { return name;

} public void setName(String name) { this.name = name;
}
}
/<string>

輸入參數簽名認證

我們請求的接口是通過http/https傳輸的,一旦參數被攔截,很有可能被黑客篡改,並傳回給服務端,為了防止這種情況發生,我們需要對參數進行簽名認證,保證傳回的參數是合法用戶請求的,具體思路如下:

請求接口前,將token、timstamp和接口需要的參數按照ASCII升序排列,拼接成url=key1=value1&key2=value2,如:name=xxx&timestamp=xxx&token=xxx,進行MD5(url+salt),得到signature,將token、signature、timestamp放到請求頭傳給服務端,如header(“token”,token),header(“timestamp”,timestamp),header(“signature”,signature)。

(注:salt即為動態獲取的密鑰)

下面請看具體的實現,應該在攔截器裡統一處理:

public class ApiInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ApiInterceptor.class); private String salt="ed4ffcd453efab32"; @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
logger.info("進入攔截器");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "application/json;charset=utf8");
StringBuilder urlBuilder = getUrlAuthenticationApi(request); //這裡是MD5加密算法
String sign = MD5(urlBuilder.toString() + salt);
String signature = request.getHeader("signature");
logger.info("加密前傳入的簽名" + signature);
logger.info("後端加密後的簽名" + sign); if(sign.equals(signature)){ return true;

}else { //簽名錯誤
response.getWriter().print("簽名錯誤");
response.getWriter().close(); return false;
}
} private StringBuilder getUrlAuthenticationApi(HttpServletRequest request) {
Enumeration<string> paramesNames = request.getParameterNames();
List<string> nameList = new ArrayList<>();
nameList.add("token");
nameList.add("timestamp"); while (paramesNames.hasMoreElements()){
nameList.add(paramesNames.nextElement());
}
StringBuilder urlBuilder = new StringBuilder();
nameList.stream().sorted().forEach(name -> { if ("token".equals(name) || "timestamp".equals(name)){ if("token".equals(name) && null ==request.getHeader(name)){ return;
}
urlBuilder.append('&');
urlBuilder.append(name).append('=').append(request.getHeader(name));
} else {
urlBuilder.append('&');
urlBuilder.append(name).append('=').append(request.getParameter(name));
}
});
urlBuilder.deleteCharAt(0);
logger.info("url : " + urlBuilder.toString()); return urlBuilder;
} @Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
} @Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
/<string>/<string>

輸入輸出參數加密

為了保護數據,比如說爬蟲,因此需要對輸入輸出參數進行加密,客戶端加密輸入參數傳回服務端,服務端解密輸入參數執行請求;服務端返回數據時對其加密,客戶端拿到數據後解密數據,獲取最終的數據。這樣,即便別人知道了參數地址,也無法模擬請求數據。

以上就是作者總結的一些提升安全的方式,當然,世界上沒有絕對安全的系統,大家有什麼其他方式也可以給我留言,咱們可以一起討論討論。


分享到:


相關文章: