天天看點

解決安全頭部 X-Content-Type-Options 導緻 jsonp 無法加載的問題

作者:馬小聰

前言

幾天前大聰明上線三年的應用系統被掃描出了漏洞,本以為增加 X-Content-Type-Options 安全頭就可以完美解決漏洞萬萬沒想到,增加了 X-Content-Type-Options:nosniff 響應頭以後卻引發了另一個問題...

坎坷的解決之路

問題描述及原因

在應用中有一個頁面,在這個頁面中需要用 Ajax 調用背景的接口擷取資料(因為在生産環境中涉及到了跨域,是以在 Ajax 中使用了 jsonp 來解決跨域的問題),但是增加了 X-Content-Type-Options 響應頭以後卻導緻了 Ajax 無法正常運作。遇到這個問題的時候,大聰明第一反應就是找 X-Content-Type-Options:nosniff 響應頭的“麻煩”,畢竟這個問題是在增加響應頭後才出現的,接下來就在本地複現一下這個問題

$(document).ready(function(){
console.log("開始請求");
	$.ajax({
		 url:"http://localhost:8081/searchweb/search/hotWord",
		 type : 'get',
		 cache : false,
		 async : true,
		 dataType : "jsonp",
		 timeout:30000,
		 jsonpCallback:"callback",
			 processdata:false,
		 data:{
			 siteid:"97",
		 },
		 success: function(jsonData){
			console.log("請求成功");
			console.log("jsonData:"+jsonData);
			
		},
		error: function(XMLHttpRequest, textStatus, errorThrown) {
			   console.log("狀态碼:"+XMLHttpRequest.status);//狀态碼
			   console.log("狀态:"+XMLHttpRequest.readyState);//狀态
			   console.log("錯誤資訊:"+textStatus);//錯誤資訊
		}
	  
	});
})           
@RequestMapping(value = "/search/hotWord", method = RequestMethod.GET ,produces="application/json;charset=UTF-8,text/html;charset=UTF-8")
@ResponseBody
public JSONPObject hotWord(HttpServletRequest request, HttpServletResponse response, @RequestParam int siteid, @RequestParam String callback){
	
	List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
	if(siteid == 97){
		list = searchService.listHotWords(0);
		Map<String, String> map = new HashMap<String, String>();
		for(int i = 1;i <= 4;i++ ){
			map.put("word_"+i, (String) list.get(i).get("words"));
		}
		System.out.println("map --->"+JSON.toJSONString(map));
		return new JSONPObject(callback, map);
	
	}else{
		list = searchService.listHotWords(5);
		Map<String, String> map = new HashMap<String, String>();
		for(int i = 1;i <= 4;i++ ){
			map.put("word_"+i, (String) list.get(i).get("words"));
		}
		return new JSONPObject(callback, map);
		
	}
}
           
解決安全頭部 X-Content-Type-Options 導緻 jsonp 無法加載的問題

果然不出所料,就是由于添加了 X-Content-Type-Options: nosniff 響應頭導緻出現了上面的問題,後來通過百度找到了問題的根本原因

  • 如果通過 styleSheet 參考檢索到的響應中接收到 "nosniff" 指令,則 Windows Internet Explorer 不會加載“stylesheet”檔案,除非 MIME 類型比對 "text/css"。
  • 如果通過 script 參考檢索到的響應中接收到 "nosniff" 指令,則 Internet Explorer 不會加載“script”檔案,除非 MIME 類型比對以下九個值之一:"application/ecmascript"、"application/javascript"、"application/x-javascript"、"text/ecmascript"、"text/javascript"、"text/jscript"、"text/x-javascript"、"text/vbs"、"text/vbscript"。

現在已經知道了問題的根本原因,那我們就開始着手解決它

解決思路

俗話說“繁瑣問題必有猥瑣解法”,我們可以用一個最“猥瑣”的辦法來完美解決這個問題——删掉 X-Content-Type-Options: nosniff 響應頭,很顯然這個辦法是不靠譜的,各位小夥伴當個笑話看看就行了

咱們言歸正傳,在上面的代碼中我們可以看到,我們指定的 Content-Type 值是 application/json,很明顯是不比對上述九個值的,那我們首先就要對 Content-Type 值進行調整,替換成符合項目需求且滿足上述九種類型之一的值。針對上述接口來說,接口的傳回值并不一定要是 Json 格式,傳回一個 String 類型的字元串也可以滿足業務需求,那麼我們就可以用 text/javascript 來替換 application/json。接下來我們再看看浏覽器抛出的錯誤提示:callback was not called,翻譯過來就是“回調方法未被調用”,既然未被調用,那索性就直接把回調方法也就是 Ajax 中的 callback 删掉,callback 被删除掉以後,大聰明再看 Jsonp 也有點不順眼了,順手就把 Jsonp 改成了 Json ~ 上文中我們提到了,這個接口的調用涉及到了跨域的問題,我們既然把 Ajax 中的 Jsonp 改成了 Json,那麼我們就得把跨域的問題放在接口中解決,此時我們也就走到了解決問題的最後一步 —— 在接口中增加一行代碼來解決跨域問題:response.setHeader("Access-Control-Allow-Origin", "*")。

完整代碼

至此由于增加安全頭部 X-Content-Type-Options 而導緻 jsonp 無法加載的問題就被完美解決了✌,解決問題的思路已經說完了,最後給各位小夥伴貼一下完整的代碼

$(document).ready(function(){
console.log("開始請求");
	$.ajax({
		 url:"http://localhost:8081/searchweb/search/hotWord",
		 type : 'get',
		 cache : false,
		 async : true,
		 dataType : "json",
		 timeout:30000,
		 data:{
			 siteid:"97",
		 },
		 success: function(jsonData){
			console.log("請求成功");
			console.log("jsonData:"+jsonData);
		},
		error: function(XMLHttpRequest, textStatus, errorThrown) {
			   console.log("狀态碼:"+XMLHttpRequest.status);//狀态碼
			   console.log("狀态:"+XMLHttpRequest.readyState);//狀态
			   console.log("錯誤資訊:"+textStatus);//錯誤資訊
		}
	  
	});
})
複制代碼           
@RequestMapping(value = "/search/hotWord", method = RequestMethod.GET ,produces = "text/javascript;charset=UTF-8")
@ResponseBody
public Object hotWord(HttpServletRequest request, HttpServletResponse response, @RequestParam int siteid){
	
	List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
	if(siteid == 97){
		list = searchService.listHotWords(0);
		Map<String, String> map = new HashMap<String, String>();
		for(int i = 1;i <= 4;i++ ){
			map.put("word_"+i, (String) list.get(i).get("words"));
		}
		response.setHeader("Access-Control-Allow-Origin", "*");     //允許跨域請求
		return JSON.toJSONString(map);
	}else{
		list = searchService.listHotWords(5);
		Map<String, String> map = new HashMap<String, String>();
		for(int i = 1;i <= 4;i++ ){
			map.put("word_"+i, (String) list.get(i).get("words"));
		}
		return JSON.toJSONString(map);
	}
}           

小結

本人經驗有限,有些地方可能講的沒有特别到位,如果您在閱讀的時候想到了什麼問題,歡迎在評論區留言,我們後續再一一探讨‍

希望各位小夥伴動動自己可愛的小手,來一波點贊+關注 (✿◡‿◡) 讓更多小夥伴看到這篇文章~ 蟹蟹呦(●'◡'●)

如果文章中有錯誤,歡迎大家留言指正;若您有更好、更獨到的了解,歡迎您在留言區留下您的寶貴想法。

繼續閱讀