ajax跨域解決辦法
跨域的安全限制都是指瀏覽器端來說的.伺服器端是不存在跨域安全限制的,
什麼是跨域:
跨域是指從一個域名的網頁去請求另一個域名的資源。比如從www.baidu.com 頁面去請求 www.google.com 的資源。跨域的嚴格一點的定義是:只要 協議,域名,埠有任何一個的不同,就被當作是跨域
為什麼瀏覽器要限制跨域訪問呢?
原因就是安全問題:如果一個網頁可以隨意地訪問另外一個網站的資源,那麼就有可能在客戶完全不知情的情況下出現安全問題。比如下面的操作就有安全問題:
- 使用者訪問www.mybank.com ,登陸並進行網銀操作,這時cookie啥的都生成並存放在瀏覽器
- 使用者突然想起件事,並迷迷糊糊地訪問了一個邪惡的網站 www.xiee.com
- 這時該網站就可以在它的頁面中,拿到銀行的cookie,比如使用者名稱,登陸token等,然後發起對www.mybank.com 的操作。
- 如果這時瀏覽器不予限制,並且銀行也沒有做響應的安全處理的話,那麼使用者的資訊有可能就這麼洩露了。
為什麼要跨域
既然有安全問題,那為什麼又要跨域呢?有時公司內部有多個不同的子域,比如一個是location.company.com ,而應用是放在app.company.com , 這時想從 app.company.com去訪問 location.company.com 的資源就屬於跨域。
跨域訪問需要的兩件寶貝
由於瀏覽器一般不對script,img等進行跨域限制,所以我們有機會通過script的方式來實現跨域訪問。
跨域訪問需要用到兩樣東東,一個是JSON,一種基於文字的傳輸協議;一種是JSONP,一群碼農想出來的跨域解決方案。關於JSON與JSONP的解釋,可以參考 JSON & JSONP
為了便於客戶端使用資料,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許使用者傳遞一個callback引數給服務端,然後服務端返回資料時會將這個callback引數作為函式名來包裹住JSON資料,這樣客戶端就可以隨意定製自己的函式來自動處理返回資料了。
由 於此前很少寫前端的程式碼(哈哈,不合格的程式設計師啊),最近專案中用到json作為系統間互動的手段,自然就伴隨著眾多ajax請求,隨之而來的就是要解決 ajax的跨域問題。本篇將講述一個小白從遇到跨域不知道是跨域問題,到知道是跨域問題不知道如何解決,再到解決跨域問題,最後找到兩種方法解決ajax 跨域問題的全過程。
不知是跨域問題
起 因是這樣的,為了複用,減少重複開發,單獨開發了一個使用者許可權管理系統,共其他系統獲取認證與授權資訊,暫且稱之為A系統;呼叫A系統以B為例。在B系統 中用ajax呼叫A系統系統的介面(資料格式為json),當時特別困惑,在A系統中訪問相應的url可正常回返json資料,但是在B系統中使用 ajax請求同樣的url則一點兒反應都沒有,好像什麼都沒有發生一樣。這樣反反覆覆改來改去好久都沒能解決,於是求救同事,提醒可能是ajax跨域問 題,於是就將這個問題當做跨域問題來解決了。
知跨域而不知如何解決
知道問題的確切原因,剩下的就是找到解決問題的方法了。google了好久,再次在同事的指點下知道jQuery的ajax有jsonp這樣的屬性可以用來解決跨域的問題。
1.第一種解決方式
總結jsonp步驟:
step1:頁面
js請求中url請求末尾加callback=?引數。使用 JSONP 形式呼叫函式時,如 "myurl?callback=?" jQuery 將自動替換 ? 為正確的函式名,以執行回撥函式。
js返回引數dataType:標明為jsonp
step2:伺服器修改
拼舊的返回資料,在外層加callback字串
這樣應該就ok了!
現在也知道了怎樣來解決跨域問題,餘下的就是實現的細節了。實現的過程中錯誤還是避免不了的。由於不瞭解json和jsonp兩種格式的區別,也犯了錯誤,google了好久才解決。
首先來看看在頁面中如何使用jQuery的ajax解決跨域問題的簡單版:
$(document).ready(function(){
var url='http://localhost:8080/WorkGroupManagment/open/getGroupById"
+"?id=1&callback=?';
$.ajax({
url:url,
dataType:'jsonp',
processData: false,
type:'get',
success:function(data){
alert(data.name);
},
error:function(XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.status);
alert(XMLHttpRequest.readyState);
alert(textStatus);
}});
});
這樣寫是完全沒有問題的,起先error的處理函式中僅僅是alert(“error”),為了進一步弄清楚是什麼原因造成了錯誤,故將處理函式變 為上面的實現方式。最後一行alert使用為;parsererror。百思不得其解,繼續google,最終還是在萬能的stackoverflow找 到了答案,連結在這裡。原因是jsonp的格式與json格式有著細微的差別,所以在server端的程式碼上稍稍有所不同。
比較一下json與jsonp格式的區別:
json格式:
{
"message":"獲取成功",
"state":"1",
"result":{"name":"工作組1","id":1,"description":"11"}
}
jsonp格式:
callback({
"message":"獲取成功",
"state":"1",
"result":{"name":"工作組1","id":1,"description":"11"}
})
看出來區別了吧,在url中callback傳到後臺的引數是神馬callback就是神馬,
jsonp比json外面有多了一層,callback()。
這樣就知道怎麼處理它了。於是修改後臺程式碼。
後臺java程式碼最終如下:
@RequestMapping(value = "/getGroupById")
public String getGroupById(@RequestParam("id") Long id,
HttpServletRequest request, HttpServletResponse response)
throws IOException {
String callback = request.getParameter("callback");
ReturnObject result = null;
Group group = null;
try {
group = groupService.getGroupById(id);
result = new ReturnObject(group, "獲取成功", Constants.RESULT_SUCCESS);
} catch (BusinessException e) {
e.printStackTrace();
result = new ReturnObject(group, "獲取失敗", Constants.RESULT_FAILED);
}
String json = JsonConverter.bean2Json(result);
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
out.print(callback + "(" + json + ")");
return null;
}
注意這裡需要先將查詢結果轉換我json格式,然後用引數callback在json外面再套一層,就變成了jsonp。
指定資料型別為jsonp的ajax就可以做進一步處理了。
雖然這樣解決了跨域問題,還是回顧下造成parsererror的原因。原因在於盲目的把json格式的資料當做jsonp格式的資料讓ajax處理,造成了這個錯誤,此時server端程式碼是這樣的:
@RequestMapping(value = "/getGroupById")
@ResponseBody
public ReturnObject getGroupById(@RequestParam("id") Long id,
HttpServletRequest request, HttpServletResponse response){
String callback = request.getParameter("callback");
ReturnObject result = null;
Group group = null;
try {
group = groupService.getGroupById(id);
result = new ReturnObject(group, "獲取成功", Constants.RESULT_SUCCESS);
} catch (BusinessException e) {
e.printStackTrace();
result = new ReturnObject(group, "獲取失敗", Constants.RESULT_FAILED);
}
return result;
}
至此解決ajax跨域問題的第一種方式就告一段落。
2.第二種解決方式【通過設定Access-Control-Allow-Origin來實現跨域訪問比較簡單。】
修改controller在被請求的Response header中加入
在程式中新增
response.addHeader("Access-Control-Allow-Origin","*");//表示允許任何域名跨域訪問
如果需要指定某域名才允許跨域訪問,
只需把Access-Control-Allow-Origin:*改為Access-Control-Allow-Origin:允許的域名 例如:
response.addHeader('Access-Control-Allow-Origin:http://www.client.com');
@RequestMapping("findCourseNoteByKeywords")
@ResponseBody
public ApiResult findCourseNoteByKeywords(String keyWord,HttpServletResponse response){
response.addHeader("Access-Control-Allow-Origin","*");
ApiResult apiResult = new ApiResult();
List<Map<String, Object>> result = bCourseNoteService.findCourseNotesByKeywords(keyWord);
Map map = new HashMap<String, List<Map<String, Object>> >();
map.put("data", result);
apiResult.setData(map);
return apiResult;
}
3.追加一種解決方式
追求永無止境,在google的過程中,無意中發現了一個
專門用來解決跨域問題的jQuery外掛-jquery-jsonp。
有第一種方式的基礎,使用jsonp外掛也就比較簡單了,server端程式碼無需任何改動。
來看一下如何使用jquery-jsonp外掛解決跨域問題吧。
var url="http://localhost:8080/WorkGroupManagment/open/getGroupById"
+"?id=1&callback=?";
$.jsonp({
"url": url,
"success": function(data) {
$("#current-group").text("當前工作組:"+data.result.name);
},
"error": function(d,msg) {
alert("Could not find user "+msg);
}
});
SONP方法是一種非官方方法,而且這種方法只支援GET方式,不如POST方式安全。
即使使用jQuery的jsonp方法,type設為POST,也會自動變為GET。
三種方案比較;
方法1需要改動:頁面+伺服器端程式碼
方法2需要改動:伺服器端程式碼
方法3需要改動:頁面(不過需要引入jquery的外掛)
這裡針對ajax與jsonp的異同再做一些補充說明:
1、ajax和jsonp這兩種技術在呼叫方式上“看起來”很像,目的也一樣,都是請求一個url,然後把伺服器返回的資料進行處理,因此jquery和ext等框架都把jsonp作為ajax的一種形式進行了封裝;
2、但ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態新增<script>標籤來呼叫伺服器提供的js指令碼。
3、所以說,其實ajax與jsonp的區別不在於是否跨域,ajax通過服務端代理一樣可以實現跨域,jsonp本身也不排斥同域的資料的獲取。
4、還有就是,jsonp是一種方式或者說非強制性協議,如同ajax一樣,它也不一定非要用json格式來傳遞資料,如果你願意,字串都行,只不過這樣不利於用jsonp提供公開服務。
總而言之,jsonp不是ajax的一個特例,哪怕jquery等巨頭把jsonp封裝進了ajax,也不能改變著一點!