前期回顧:讓controller沒有祕密
1.跨域是啥?
跨域問題是web開發中很經典的一個問題,我們先來重現一下這個問題,讓大家能夠快速理解(只做重要代碼說明)
首先我們來準備兩個web項目,兩個項目分別在tomcat不同而端口部署,第一個web項目我們只寫一個提供一個簡單信息的接口,如下:
/**
* @author swing
*/
@Slf4j
@Controller
@RequestMapping("/info")
public class WeatherController {
@GetMapping
@ResponseBody
public String weather() {
return "important information";
}
}
然後我們啓動這個項目,將他部署在tomcat的8099端口,接口的信息如下:
URL:http://localhost:8099/info
Response:important information
然後我們回到主項目來,編寫如下頁面:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome!</title>
<script type="text/javascript">
function change() {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let divElement = document.getElementById("div1");
divElement.innerHTML = xhr.responseText;
divElement.style.color = "red";
}
};
//記得轉碼
xhr.open("get", "http://localhost:8099/info", true);
xhr.send();
}
</script>
</head>
<body>
<div id="div1"></div>
<button onclick="change()">Get Info</button>
<h2><a href="http://localhost:8099/info">Get Info</a> </h2>
</body>
</html>
然後我們啓動這個項目,將他部署在tomcat的8080端口上,可以看出,我這裏分別使用ajax和一個鏈接標籤去請求這個接口,我們來分別看一下運行結果:
點擊鏈接標籤中的Get Info 頁面成功跳轉並獲取正確信息:
當點擊 按鈕進行ajax請求時,翻車了:
這時候就發生了跨域問題,由於瀏覽器的同源機制,當一個網站試圖在自己的域下去請求另外一個域的信息,從而引起這個錯誤,大家要注意:這裏的域和和域名的域還是有區別的,同域(或同源)指的是 域名,子域名,端口,協議 都相同。
那麼鏈接標籤<a>是怎麼可以請求到接口呢?很明顯,他已經直接跳出本域,到達請求目標的域(觀察地址欄),因此不涉及跨域問題。
2.如何搞定?
簡單瞭解了跨域問題,我們來說說如何搞定它!
常見的解決方案如下:
2.1.設置接口提供方允許跨域訪問
這裏我們可以使用一個過濾器,爲響應增加一些頭部信息:
/**
* @author swing
*/
public class AllowOrigin implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(request, httpServletResponse);
}
@Override
public void destroy() {
}
}
此時瀏覽器在接收到該網站的訪問後,便不會阻難
2.2.後臺請求轉發
這個解決方案也很好理解,既然在瀏覽器中我的請求收到限制,那我爲何不找後端幫我代理一下,我想要啥數據跟他說,然後他替我請求,簡單給出handler代碼:
@GetMapping("info")
@ResponseBody
public String getInfo() {
String httpUrl = "http://localhost:8099/info";
return request(httpUrl, null);
}
/**
* @param httpUrl :請求接口
* @param httpArg :參數
* @return 返回結果
*/
public static String request(String httpUrl, String httpArg) {
BufferedReader reader = null;
String result = null;
StringBuilder sbf = new StringBuilder();
httpUrl = httpUrl + "?" + httpArg;
try {
URL url = new URL(httpUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream is = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String strRead = null;
while ((strRead = reader.readLine()) != null) {
sbf.append(strRead);
sbf.append("\r\n");
}
reader.close();
result = sbf.toString();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
2.3.CORS 後臺同源配置(SpringMVC提供)(這個不錯喲!)
spring爲我們提供了一種簡便的的方法來解決跨域問題:@CrossOrigin 註解
/**
* @author swing
*/
@Slf4j
@Controller
@RequestMapping("/info")
public class WeatherController {
@CrossOrigin(origins = {}, methods = {}, maxAge = 3600, allowedHeaders = {}, exposedHeaders = {})
@GetMapping
@ResponseBody
public String weather() {
return "important information";
}
}
該註解也可作用在類上(表示作用於該類的所有Handler),其中有幾個屬性這裏說一下:
- origins:允許的跨域訪問的站點,相當於 header中的 Access-Control-Allow-Origin 信息,如果設置爲" * "則表示所有站點都可以
- methods:即Access-Control-Allow-Methods ,允許的請求方法,GET,POST,PUT,DELETE。
- maxAge:即 Access-Control-Max-Age
瀏覽器的同源策略,就是出於安全考慮,瀏覽器會限制從腳本發起的跨域HTTP請求(比如異步請求GET, POST, PUT, DELETE, OPTIONS等等),所以瀏覽器會向所請求的服務器發起兩次請求,第一次是瀏覽器使用OPTIONS方法發起一個預檢請求,第二次纔是真正的異步請求,第一次的預檢請求獲知服務器是否允許該跨域請求:如果允許,才發起第二次真實的請求;如果不允許,則攔截第二次請求。
Access-Control-Max-Age用來指定本次預檢請求的有效期,單位爲秒,,在此期間不用發出另一條預檢請求
- allowedHeaders & exposedHeaders:即 Access-Control-Allow-Headers,表示允許或者排除有某些頭部的請求
當然,CORS還支持一個全局配置,在MvcConfig中實現WebMvcConfigurer.addCorsMappings方法即可,如下:
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/info")
.allowedOrigins("http://localhost:8080")
.allowedMethods("PUT", "GET", "POST")
.allowCredentials(true)
.maxAge(3600);
}