備注:有時候項目有點忙,都忘記思考,一次面試中提出的跨域的問題,才恍然大悟,做了一下小的總結,望指正!
過濾攔截請求!!!
由于工程合作開發的需要,后臺的應用要能支持跨域訪問,但是在這個跨域訪問“時好時壞”,我們這幫屌絲所知道的就是加上兩個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的跨域問題,也許還有更好的解決方案,可以提出來大家一起學習學習。
最好的方案是最符合當前需求且易于擴展的。