跨域問題

備注:有時候項目有點忙,都忘記思考,一次面試中提出的跨域的問題,才恍然大悟,做了一下小的總結,望指正!

過濾攔截請求!!!

由于工程合作開發的需要,后臺的應用要能支持跨域訪問,但是在這個跨域訪問“時好時壞”,我們這幫屌絲所知道的就是加上兩個jar包,然后聲明一下Filter,感覺很簡單的有沒有!!感覺自己很牛X有沒有!!全是幻覺!!要不然怎么會時好時壞!!為了深入了解這個問題,決定寫這篇文章總結一下。

要知道跨域請求就要先了解同源策略,那么什么是同源?什么是不同源?簡單來說就是,如果兩個資源,包括HTML頁面、JavaScript腳本、css樣式,對應的協議、域名和端口完全相同,那么這兩個資源就是同源的,Same-origin policy解釋得很清楚。那么同源策略的意思就是一個源中的資源訪問另外一個源中的資源,在在這一點上JavaScript的跨站資源訪問表現的更加明顯。在HTML5之前Ajax是不允許發起跨站請求的,如果有需求的話,可以使用JSONP等方法,但是缺點就是:

只支持Get不支持Post;

本質上是腳本注入的方式,存在安全隱患;

還有JSONP的優缺點,但是自從HTML5出現之后,提出了CORS(跨站資源共享)這種方式,極大地方便了日常的開發。如果要理解CORS的工作原理,首先要知道跨域訪問是怎么被禁止的,之前本屌絲一直以為是前臺的跨域訪問請求不能發出去,是實現同源策略的瀏覽器攔截了該請求,但是后來才知道瀏覽器并沒有攔截請求,而是攔截了服務器端返回的響應。

所以如果要支持跨域訪問,需要瀏覽器和后臺服務器程序同時支持,如果這兩個條件不能同時滿足,則還是不能支持跨域訪問。

用于CORS中的Http的首部有如下幾個:

響應頭

Access-Control-Allow-Origin: 允許跨域訪問的域,可以是一個域的列表,也可以是通配符”*”;

Access-Control-Allow-Methods: 允許使用的請求方法,以逗號隔開;

Access-Control-Allow-Headers: 允許自定義的頭部,以逗號隔開,大小寫不敏感;

Access-Control-Expose-Headers: 允許腳本訪問的返回頭,請求成功后,腳本可以在XMLHttpRequest中訪問這些頭的信息

Access-Control-Allow-Credentials: 是否允許請求帶有驗證信息,XMLHttpRequest請求的withCredentials標志設置為true時,認證通過,瀏覽器才將數據給腳本程序。

Access-Control-Max-Age: 緩存此次請求的秒數。在這個時間范圍內,所有同類型的請求都將不再發送預檢請求而是直接使用此次返回的頭作為判斷依據,非常有用,大幅優化請求次數;

請求頭

Origin: 普通的HTTP請求也會帶有,在CORS中專門作為Origin信息供后端比對,表明來源域,要與響應頭中的Access-Control-Allow-Origin相匹配才能進行跨域訪問;

Access-Control-Request-Method: 將要進行跨域訪問的請求方法,要與響應頭中的Access-Control-Allow-Methods相匹配才能進行跨域訪問;

Access-Control-Request-Headers: 自定義的頭部,所有用setRequestHeader方法設置的頭部都將會以逗號隔開的形式包含在這個頭中,要與響應頭中的Access-Control-Allow-Headers相匹配才能進行跨域訪問

從支持跨域訪問的范圍說,可以有整個服務器、單個應用程序、單個接口。

java服務器端解決跨域問題:現在很多開發的API都支持ajax直接請求,這樣就會導致跨域的問題,解決跨域的問題一方面可以從前端,另一方面就是服務器端。

既然是搞服務器端,做對外的API服務,當然是做到越簡單越好,前端只需要傻傻的使用就好。

目前我接觸來的情況是有2種實現方式,下面直接代碼,你們根據自己項目情況,選擇或者修改其中的代碼,所有代碼都是項目實戰中運行的。

第一種情況,比較簡單,讓所有的controller類繼承自定義的BaseController類,改類中將對返回的頭部做些特殊處理。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

public abstract class BaseController {

? /**

? ? * description:send the ajax response back to the client side

? ? * @param responseObj

? ? * @param response

? ? */

? ? protected void writeAjaxJSONResponse(Object responseObj, HttpServletResponse response) {

? ? ? ? response.setCharacterEncoding("UTF-8");

? ? ? ? response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1

? ? ? ? response.setHeader("Pragma", "no-cache"); // HTTP 1.0

? ? ? ? /**

? ? ? ? * for ajax-cross-domain request TODO get the ip address from

? ? ? ? * configration(ajax-cross-domain.properties)

? ? ? ? */

? ? ? ? response.setHeader("Access-Control-Allow-Origin", "*");

? ? ? ? response.setDateHeader("Expires", 0); // Proxies.

? ? ? ? PrintWriter writer = getWriter(response);

? ? ? ? writeAjaxJSONResponse(responseObj, writer);

? ? }

? /**

? ? *

? ? * @param response

? ? * @return

? ? */

? ? protected PrintWriter getWriter(HttpServletResponse response) {

? ? ? ? if(null == response){

? ? ? ? ? ? return null;

? ? ? ? }

? ? ? ? PrintWriter writer = null;

? ? ? ? try {

? ? ? ? ? ? writer = response.getWriter();

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? logger.error("unknow exception", e);

? ? ? ? }

? ? ? ? return writer;

? ? }

? ? /**

? ? * description:send the ajax response back to the client side.

? ? *

? ? * @param responseObj

? ? * @param writer

? ? * @param writer

? ? */

? ? protected void writeAjaxJSONResponse(Object responseObj, PrintWriter writer) {

? ? ? ? if (writer == null || responseObj == null) {

? ? ? ? ? ? return;

? ? ? ? }

? ? ? ? try {? ? ? ? writer.write(JSON.toJSONString(responseObj,SerializerFeature.DisableCircularReferenceDetect));

? ? ? ? } finally {

? ? ? ? ? ? writer.flush();

? ? ? ? ? ? writer.close();

? ? ? ? }

? ? }

}

接下來就是我們自己業務的controller了,其中主要是要調用 writeAjaxJSONResponse(result, response);這個方法

?

1

2

3

4

5

6

7

8

9

10

11

@Controller

@RequestMapping(value = "/account")

public class AccountController extends BaseController {

@RequestMapping(value = "/add", method = RequestMethod.POST)

? ? public void addAccount(HttpSession session,HttpServletRequest request,HttpServletResponse response){

? ? ? ? ViewerResult result = new ViewerResult();

? ? ? ? //實現自己業務邏輯代碼

? ? ? ? writeAjaxJSONResponse(result, response);

? ? }

}

?

1

2

3

4

好了,這種簡單的方式就實現了。

接下來介紹第二種方式,filter。我們在寫springMVC的時候,更喜歡的方式是通過@ResponseBody給返回對象進行封裝直接返回給前端,這樣簡單而且容易。

如果使用@ResponseBody就不能使用第一種方法了,所有就使用filter給所有的請求都封裝一下跨域,接下來直接實現代碼:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

public class HeadersCORSFilter implements Filter {

? ? @Override

? ? public void init(FilterConfig filterConfig) throws ServletException {

? ? ? ? // TODO Auto-generated method stub

? ? }

? ? @Override

? ? public void doFilter(ServletRequest request, ServletResponse servletResponse,

? ? ? ? ? ? FilterChain chain) throws IOException, ServletException {

? ? ? ? HttpServletResponse response = (HttpServletResponse) servletResponse;

? ? ? ? ? ? response.setHeader("Access-Control-Allow-Origin", "*");

? ? ? ? ? ? response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");

? ? ? ? ? ? response.setHeader("Access-Control-Max-Age", "3600");

? ? ? ? ? ? response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization");

? ? ? ? ? ? response.setHeader("Access-Control-Allow-Credentials","true");

? ? ? ? ? ? chain.doFilter(request, servletResponse);

? ? }

? ? @Override

? ? public void destroy() {

? ? ? ? // TODO Auto-generated method stub

? ? }

}

好了,filter實現了,然后就是要在web.xml里面把這個filter運用起來了。

打開項目的web.xml,填寫下面的幾行代碼:

?

1

2

3

4

5

6

7

8

<filter>

? <filter-name>cors</filter-name>

? <filter-class>xxx.xxxx.xxxxx.xxxx.HeadersCORSFilter</filter-class><!--你過濾器的包 -->

</filter>

<filter-mapping>

? <filter-name>cors</filter-name>

? <url-pattern>/open/*</url-pattern><!-- 你開放的接口前綴? -->

</filter-mapping>

?

1

2

好了,通過上面的2種方式,可以解決百分之80的跨域問題,也許還有更好的解決方案,可以提出來大家一起學習學習。

最好的方案是最符合當前需求且易于擴展的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容