天天看点

解决安全头部 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);
	}
}           

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

继续阅读