此文講解在RESTful API中跨域問題在項目中如何處理的!html
CORS 是 Cross Origin Resource Sharing 的縮寫, 定義了瀏覽器和服務器間共享內容的新方式,經過它瀏覽器和服務器能夠安全地進行跨域訪問,它是 JSONP 的現代繼任 者。服務器上的 CORS 配置能夠精細地指定容許跨域訪問的條件:來源域、HTTP 方法、請求頭、內容類型等等。並 且,CORS 讓 XMLHttpRequest 也能夠跨域,咱們能夠像往常同樣編寫 AJAX 調用代碼。全部現代瀏覽器都支持 CORS,因此你應該能夠放心地使用它,只有在須要兼容老舊瀏覽器的場合,才用 JSONP作fallback。支持 CORS 的瀏覽器在嘗試進行跨域 XMLHttpRequest 時,會先發出一個「事前檢查」,就是一個OPTIONS 請求,其中會包括一些有用的請求頭:java
Access-Controll-Request-Headers: accept, content-type Access-Controll-Request-Method: POST
接着服務器會作出響應:node
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept Access-Control-Max-Age: 1728000
最後瀏覽器會根據服務器的響應頭,判斷請求是否在服務器規定的範圍內。好比來源是否在 Access-Control-Allow- Origin 裏,HTTP 方法是否是在 Access-Control-Allow-Methods 裏面,有沒有不在 Allow-Headers 裏面的請求頭。若是以上條件都符合,那麼瀏覽器就會放行此次請求,而且在Access-Control-Max-Age 指定的時間內(單位是秒,以上設置的是 20 天)不須要再進行這種「事前檢查」。json
在以上服務器響應頭中,Access-Control-Allow-Headers 不可使用通配符。因此若是你要容許全部請求頭,不妨把瀏覽器發來的 Access-Control-Request-Header 直接返回。跨域
事實上,若是跨域請求是「簡單請求」,也就是 HTTP 方法爲 GET、HEAD、POST,請求體的 MIME Type 是如下其中一種:application/x-www-form-urlencoded、multipart/form-data 或者text/plain,而且沒有自定義的請求頭。這時瀏覽器只根據請求頭中的 Origin 和服務器返回的Access-Control-Allow-Origin 就能夠判斷了。但咱們是 RESTful API,請求體是application/json,因此只能用上面那種「事前檢查」的方式。另外,利用 CORS 還能夠在跨域請求中發送 Cookie,這個特性是頗有用的。只須要爲 XMLHttpRequest 對象設置 withCredentials 屬性:瀏覽器
var xhr = new XMLHttpRequest(); xhr.withCredentials = true; java在resteasy中設置 response.getOutputHeaders().putSingle("Access-Control-Allow-Credentials",true);
但這種狀況下,就不能指定 Access-Control-Allow-Origin: *,而是必須指定一個來源,好比http://mydomain.com。安全
resteasy 設置以下
先提供options請求,告訴客戶端容許客戶端能夠帶什麼樣的頭信息過來。好比:
客戶要封裝一個複雜json數據來請求服務器,這時服務器須要須要容許客戶端頭信息中的content-type 爲application/json。這個過程是瀏覽器會發一次options請求,詢問v服務器是否容許
代碼以下:服務器
@OPTIONS @Path(value = "creatorunion/works") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response uploadWorks(@Context HttpRequest request, @Context HttpResponse response) { HttpHeaders header = request.getHttpHeaders(); List<String> requestHeader = header.getRequestHeader("Origin"); if(CollectionUtils.isNotEmpty(requestHeader)){ String host = requestHeader.get(0); response.getOutputHeaders().putSingle("Access-Control-Allow-Origin",host); } response.getOutputHeaders().putSingle("Access-Control-Allow-Headers","X-Requested-With, accept, origin, content-type"); response.getOutputHeaders().putSingle("Content-Type","application/json;charset=utf-8"); response.getOutputHeaders().putSingle("Access-Control-Allow-Credentials",true); response.getOutputHeaders().putSingle("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS"); return Response.status(200).entity("").build(); }
完了之後,客戶端就能夠順利請求服務器(接口:creatorunion/works)了。
但這樣寫有個問題,每一個接口都須要去設置,因爲在項目中使用了netty做爲容器須要在netty容器裏添加自定義handlers來統一處理resteasy &netty的淵源請參考:
http://docs.jboss.org/resteasy/docs/3.0.17.Final/userguide/html_single/index.html#d4e1485
代碼以下:cookie
private void start(ResteasyDeployment deployment,SecurityDomain domain) throws Exception { _netty = new MyNettyJaxrsServer(); _netty.setDeployment(deployment); _netty.setPort(_port); _netty.setRootResourcePath(_serverIP); _netty.setSecurityDomain(domain); //添加自定義handler List<ChannelHandler> customHandlers = Lists.newArrayList(new CorsHeadersChannelHandler(),new OPTIONHandler()) ; _netty.setCustomHandlers(customHandlers); _netty.start(); }
第一個app
Handler :OPTIONHandler @Sharable public class OPTIONHandler extends SimpleChannelInboundHandler<NettyHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, NettyHttpRequest request) throws Exception { if("OPTIONS".equals(request.getHttpMethod().toUpperCase())){ NettyHttpResponse response = request.getResponse(); response.reset(); response.setStatus(200); List<String> requestHeader = request.getMutableHeaders().get("Origin"); String host = ""; if(requestHeader!=null && !requestHeader.isEmpty()){ host = requestHeader.get(0); } response.getOutputHeaders().add("Access-Control-Allow-Origin", host); response.getOutputHeaders().add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS"); response.getOutputHeaders().putSingle("Access-Control-Allow-Credentials",true);//容許帶cookie訪問 response.getOutputHeaders().add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Content-Length"); if (!request.getAsyncContext().isSuspended()) { response.finish(); } } ctx.fireChannelRead(request); } }
第二個
Handler :CorsHeadersChannelHandler @Sharable public class CorsHeadersChannelHandler extends SimpleChannelInboundHandler<NettyHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, NettyHttpRequest request) throws Exception { List<String> requestHeader = request.getMutableHeaders().get("Origin"); String host = ""; if(requestHeader!=null && !requestHeader.isEmpty()){ host = requestHeader.get(0); } request.getResponse().getOutputHeaders().add("Access-Control-Allow-Origin", host); request.getResponse().getOutputHeaders().add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS"); request.getResponse().getOutputHeaders().add("Access-Control-Allow-Credentials",true);//容許帶cookie訪問 request.getResponse().getOutputHeaders().add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Content-Length"); ctx.fireChannelRead(request); } }
對於nodejs作以下配置可容許資源的跨域訪問:
設置CORS跨域訪問
app.all('*', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "X-Requested-With, accept, origin, content-type"); res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); res.header("X-Powered-By", ' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); next(); });
補充:
參考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS