SpringBoot:處理跨域請求
一、跨域背景
1.1 何為跨域?
Url
的一般格式:
協議 + 域名(子域名 + 主域名) + 埠號 + 資源地址
示例:
https://www.dustyblog.cn:8080/say/Hello
是由
https
+ www
+ dustyblog.cn
+ 8080
+ say/Hello
組成。
只要協議,子域名,主域名,埠號這四項組成部分中有一項不同,就可以認為是不同的域,不同的域之間互相訪問資源,就被稱之為跨域。
1.2 一次正常的請求
- Controller層程式碼:
@RequestMapping("/demo") @RestController public class CorsTestController { @GetMapping("/sayHello") public String sayHello() { return "hello world !"; } }
- 啟動專案,測試請求
瀏覽器開啟localhost:8080/demo/sayHello
可以打印出“hello world”
1.3 跨域測試
以Chrome為例:
開啟任意網站,如:https://blog.csdn.net
按F12,開啟【開發者工具】,在裡面的【Console】可以直接輸入js程式碼測試;
var token= "LtSFVqKxvpS1nPARxS2lpUs2Q2IpGstidMrS8zMhNV3rT7RKnhLN6d2FFirkVEzVIeexgEHgI/PtnynGqjZlyGkJa4+zYIXxtDMoK/N+AB6wtsskYXereH3AR8kWErwIRvx+UOFveH3dgmdw1347SYjbL/ilGKX5xkoZCbfb1f0=,LZkg22zbNsUoHAgAUapeBn541X5OHUK7rLVNHsHWDM/BA4DCIP1f/3Bnu4GAElQU6cds/0fg9Li5cSPHe8pyhr1Ii/TNcUYxqHMf9bHyD6ugwOFTfvlmtp6RDopVrpG24RSjJbWy2kUOOjjk5uv6FUTmbrSTVoBEzAXYKZMM2m4=,R4QeD2psvrTr8tkBTjnnfUBw+YR4di+GToGjWYeR7qZk9hldUVLlZUsEEPWjtBpz+UURVmplIn5WM9Ge29ft5aS4oKDdPlIH8kWNIs9Y3r9TgH3MnSUTGrgayaNniY9Ji5wNZiZ9cE2CFzlxoyuZxOcSVfOxUw70ty0ukLVM/78="; var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://127.0.0.1:8080/demo/sayHello'); xhr.setRequestHeader("x-access-token",token); xhr.send(null); xhr.onload = function(e) { var xhr = e.target; console.log(xhr.responseText); }
- 輸入完後直接按回車鍵就可以返回結果:
Access to XMLHttpRequest at 'http://127.0.0.1:8080/demo/sayHello'
from origin 'https://blog.csdn.net' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
該結果表明:該請求在https://blog.csdn.net
域名下請求失敗!
二、解決方案 - Cors跨域
2.1 Cors是什麼
CORS
全稱為Cross Origin Resource Sharing
(跨域資源共享), 每一個頁面需要返回一個名為Access-Control-Allow-Origin
的http頭來允許外域的站點訪問,你可以僅僅暴露有限的資源和有限的外域站點訪問。
我們可以理解為:如果一個請求需要允許跨域訪問,則需要在http
頭中設定Access-Control-Allow-Origin
來決定需要允許哪些站點來訪問。如假設需要允許https://www.dustyblog.c這個站點的請求跨域,則可以設定:
Access-Control-Allow-Origin:https://www.dustyblog.cn。
2.2 方案一:使用@CrossOrigin
註解
2.2.1 在Controller
上使用@CrossOrigin
註解
該類下的所有介面都可以通過跨域訪問
@RequestMapping("/demo2")
@RestController
//@CrossOrigin //所有域名均可訪問該類下所有介面
@CrossOrigin("https://blog.csdn.net") // 只有指定域名可以訪問該類下所有介面
public class CorsTest2Controller {
@GetMapping("/sayHello")
public String sayHello() {
return "hello world --- 2";
}
}
這裡指定當前的CorsTest2Controller
中所有的方法可以處理https://csdn.net
域上的請求,這裡可以測試一下:
- 在https://blog.csdn.net頁面開啟除錯視窗,輸入(注意:這裡請求地址是
/demo2
,請區別於1.2 案例中的/demo
)
var token= "LtSFVqKxvpS1nPARxS2lpUs2Q2IpGstidMrS8zMhNV3rT7RKnhLN6d2FFirkVEzVIeexgEHgI/PtnynGqjZlyGkJa4+zYIXxtDMoK/N+AB6wtsskYXereH3AR8kWErwIRvx+UOFveH3dgmdw1347SYjbL/ilGKX5xkoZCbfb1f0=,LZkg22zbNsUoHAgAUapeBn541X5OHUK7rLVNHsHWDM/BA4DCIP1f/3Bnu4GAElQU6cds/0fg9Li5cSPHe8pyhr1Ii/TNcUYxqHMf9bHyD6ugwOFTfvlmtp6RDopVrpG24RSjJbWy2kUOOjjk5uv6FUTmbrSTVoBEzAXYKZMM2m4=,R4QeD2psvrTr8tkBTjnnfUBw+YR4di+GToGjWYeR7qZk9hldUVLlZUsEEPWjtBpz+UURVmplIn5WM9Ge29ft5aS4oKDdPlIH8kWNIs9Y3r9TgH3MnSUTGrgayaNniY9Ji5wNZiZ9cE2CFzlxoyuZxOcSVfOxUw70ty0ukLVM/78=";
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8080/demo2/sayHello');
xhr.setRequestHeader("x-access-token",token);
xhr.send(null);
xhr.onload = function(e) {
var xhr = e.target;
console.log(xhr.responseText);
}
返回結果:
ƒ (e) {
var xhr = e.target;
console.log(xhr.responseText);
}
VM156:8 hello world --- 2
說明跨域成功!
- 換個域名測試一下看跨域是否還有效,在https://www.baidu.com按照上述方法測試一下,返回結果:
OPTIONS http://127.0.0.1:8080/demo2/sayHello 403
(anonymous)
Access to XMLHttpRequest at 'http://127.0.0.1:8080/demo2/sayHello'
from origin 'http://www.cnblogs.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
說明跨域失敗!證明該方案成功指定了部分域名能跨域!
2.3 方案二:CORS全域性配置-實現WebMvcConfigurer
- 新建跨域配置類:
CorsConfig.java
:
/**
* 跨域配置
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").
allowedOrigins("https://www.dustyblog.cn"). //允許跨域的域名,可以用*表示允許任何域名使用
allowedMethods("*"). //允許任何方法(post、get等)
allowedHeaders("*"). //允許任何請求頭
allowCredentials(true). //帶上cookie資訊
exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L); //maxAge(3600)表明在3600秒內,不需要再發送預檢驗請求,可以快取該結果
}
};
}
}
- 測試,在允許訪問的域名https://www.dustyblog.cn/控制檯輸入(注意,這裡請求的是http://127.0.0.1:8080/demo3):
var token= "LtSFVqKxvpS1nPARxS2lpUs2Q2IpGstidMrS8zMhNV3rT7RKnhLN6d2FFirkVEzVIeexgEHgI/PtnynGqjZlyGkJa4+zYIXxtDMoK/N+AB6wtsskYXereH3AR8kWErwIRvx+UOFveH3dgmdw1347SYjbL/ilGKX5xkoZCbfb1f0=,LZkg22zbNsUoHAgAUapeBn541X5OHUK7rLVNHsHWDM/BA4DCIP1f/3Bnu4GAElQU6cds/0fg9Li5cSPHe8pyhr1Ii/TNcUYxqHMf9bHyD6ugwOFTfvlmtp6RDopVrpG24RSjJbWy2kUOOjjk5uv6FUTmbrSTVoBEzAXYKZMM2m4=,R4QeD2psvrTr8tkBTjnnfUBw+YR4di+GToGjWYeR7qZk9hldUVLlZUsEEPWjtBpz+UURVmplIn5WM9Ge29ft5aS4oKDdPlIH8kWNIs9Y3r9TgH3MnSUTGrgayaNniY9Ji5wNZiZ9cE2CFzlxoyuZxOcSVfOxUw70ty0ukLVM/78=";
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8080/demo3/sayHello');
xhr.setRequestHeader("x-access-token",token);
xhr.send(null);
xhr.onload = function(e) {
var xhr = e.target;
console.log(xhr.responseText);
}
輸出結果
ƒ (e) {
var xhr = e.target;
console.log(xhr.responseText);
}
VM433:8 hello world --- 3
說明跨域成功,換個網址如https://www.baidu.com測試依舊出現需要跨域的錯誤提示,證明該配置正確,該方案測試通過。
2.3 攔截器實現
通過實現Fiter
介面在請求中新增一些Header
來解決跨域的問題
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.addHeader("Access-Control-Allow-Credentials", "true");
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAF-Authorization-Token,sessionToken,X-TOKEN");
if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) {
response.getWriter().println("ok");
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
三、更多
3.1 原始碼地址
Github 示例代