完美解決跨域問題(基於Springboot,無需前端配置)
阿新 • • 發佈:2021-01-25
技術標籤:Java webspringjavahttpajax跨域問題
在做前後端分離專案的時候,經常會遇見跨域的問題。上百度或者谷歌可以搜到很多解決方案,但把那些解決方案直接粘到自己的專案裡面用的時候會出現各種各樣的問題。這些問題的出現很耽誤進度。所以我參考這篇文章基於Springboot,自己實現了後端的跨域配置,前端無需設定proxy或者使用nginx之類的東西。
實現思路
所謂跨域是指客戶端向伺服器傳送一個http請求,伺服器向客戶端傳送http響應報文時因為客戶端和伺服器端處於兩個域下(埠號不同或者域名都不同),瀏覽器認為這個請求是不安全的,所以會直接遮蔽這次請求。因此我們需要讓伺服器的響應報文告知瀏覽器這次的跨域請求是安全的,從而使得客戶端可以收到響應報文。
需要為響應報文新增的head
- Access-Control-Allow-Origin 它的值必須要和請求中的origin(客戶端的url)相同
- Access-Control-Allow-Methods 指定Restful api
- Access-Control-Max-Age 指定存活時間
- Access-Control-Allow-Headers 指定contentType
- Access-Control-Allow-Credentials 當客戶端設定credential為true時,這個值也必須設為true,否則會影響cookie的傳送
具體流程
- 檢查請求報文的origin與預先設定的origin是否相同,如果相同,則執行步驟2否則認為這是一次非法訪問,直接返回
- 檢查請求報文的頭部method是否是Options,如果是Options或者是一次簡單請求且未設定過head,則執行步驟3,否則認為頭部資訊已經設定過了,沒有必要重複設定頭部資訊
- 為響應報文新增相應的head
具體程式碼
整體基於攔截器實現
import org.apache.commons.lang.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 這裡是跨域邏輯
*/
public class CorsFilter implements Filter {
/**
* 允許呼叫server http服務的client
* http://localhost:9528 is true
* http://localhost:9528/ not true
*/
private String origin = "http://localhost:9528";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (!isLegalRequest(request)) {
writeErrorMessage(request, response);
return;
}
handleOptions(request, response);
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 處理options方法(http預檢)
*
* @param request
* @param response
*/
private void handleOptions(HttpServletRequest request, HttpServletResponse response) {
if (!checkIsOptions(request)) {
//not options method or this request is a simple request and
//not set head
if(hasSetResponseHead(response)){
return;
}
}
//設定Http響應頭
//允許客戶端的origin訪問伺服器端
response.addHeader("Access-Control-Allow-Origin", origin);
//設定客戶端可以使用的method,使用RestfulAPI
response.addHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
//設定客戶端的存活時間1天,在存活時間內,不必再次觸發options方法
response.addHeader("Access-Control-Max-Age", String.valueOf(86400));
//設定contentType,其中X-PING為自定義type
response.addHeader("Access-Control-Allow-Headers", "X-PING");
if (request.getCookies() != null) {
//client設定了credential,所以要將Access-Control-Allow-Credentials設為true
response.addHeader("Access-Control-Allow-Credentials", "true");
}
response.addHeader("Content-Length", "0");
response.addHeader("Content-Type", "text/plain");
}
/**
* 檢查請求的合法性
*
* @param request
* @return
*/
private boolean isLegalRequest(HttpServletRequest request) {
String accessOrigin = request.getHeader("Origin");
return StringUtils.equals(origin, accessOrigin);
}
private boolean checkIsOptions(HttpServletRequest request) {
return StringUtils.equals(request.getMethod(), "OPTIONS");
}
private boolean hasSetResponseHead(HttpServletResponse response) {
return StringUtils.equals(response.getHeader("Access-Control-Allow-Origin"),
origin);
}
private void writeErrorMessage(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.addHeader("Content-Type", "text/plain");
PrintWriter writer = response.getWriter();
writer.print("This HTTP resource is not allowed for "
+ request.getHeader("Origin"));
writer.close();
response.flushBuffer();
}
}
攔截器的註冊程式碼
import com.zacharytse.wangpan.filter.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
CorsFilter corsFilter = new CorsFilter();
registrationBean.setFilter(corsFilter);
List<String> urls = new ArrayList<>();
urls.add("/*");
registrationBean.setUrlPatterns(urls);
return registrationBean;
}
}
測試介面程式碼如下
@GetMapping("/test_cors")
@ResponseBody
public String testCors() {
return "success";
}
前端使用axios做測試(前端url為http://localhost:9528,也就是請求報文中的origin)
Axios({
method:'Get',
url:'http://localhost:9000/test_cors',
contentType:'application/json;charset=UTF-8'
}).then(function(response){
console.log(response)
})
發現可以成功訪問