熟悉 web 系統開發的同學,對下面這樣的錯誤應該不會太陌生。
之所以會出現這個錯誤,是因為瀏覽器出於安全的考慮,採用同源策略的控制,防止當前站點惡意攻擊 web 伺服器盜取資料。
01、什麼是跨域請求
同源策略,簡單的說就是當瀏覽器訪問 web 伺服器資源時,只有源相同才能正常進行通訊,即協議、域名、埠號都完全一致,否則就屬於跨域請求。當發起跨域請求時,服務端是能收到請求並正常返回結果的,只是結果被瀏覽器攔截了。
像上文中,瀏覽器訪問的站點是http://127.0.0.1:8848/
,而站點內發起的介面請求源是http://localhost:8080
,因為不同源,所以報跨域請求異常。
由此可見,想要實現介面請求的正常訪問,瀏覽器的訪問站點源和介面請求源,必須得一致。
事實上,在現在流行的前後端分離的開發模式下,很難做到請求源高度一致,那怎麼辦呢?
答案肯定是有辦法啦!
雖然瀏覽器出於安全的考慮,預設採用同源策略控制,以便減少伺服器被惡意攻擊的機會,但是開發者可以透過CORS
協議在瀏覽器內實現站內跨域請求訪問。
實現很簡單,透過在 web 伺服器中增加一個特殊的Header
響應屬性來告訴瀏覽器解除跨域的限制,如果瀏覽器支援CORS
並且判斷允許透過的話,此時發起的跨域請求就可以正常展示了。
常用的 Header 響應屬性如下:
Header 屬性 | 作用 |
---|---|
Access-Control-Allow-Origin | 設定允許跨域請求的請求源,比如www.xxx.com |
Access-Control-Max-Age | 設定預檢請求的結果能被快取的時間,單位秒,比如1800 |
Access-Control-Allow-Methods | 設定允許跨域請求的方法,比如GET, POST, OPTIONS, PUT, DELETE 等 |
Access-Control-Allow-Headers | 設定允許跨域請求的頭部資訊,比如Content-Type, Accept 等 |
Access-Control-Allow-Credentials | 設定是否允許攜帶憑證(比如cookies ),引數值只能是true 或者不設定 |
帶著以上的資訊,我們就一起來了解一下如何在 Spring Boot 應用中實現跨域訪問。
02、解決方案
2.1、方法一:採用過濾器的方式全域性配置
採用過濾器的方式來實現所有介面支援跨域請求,是一種比較通用的做法,也是 Java web 專案中常用的方法,實現過程如下!
首先,建立一個實現自Filter
介面的過濾器,示例如下:
public class CrossFilter implements Filter {
/**
* 允許跨域的白名單域名
*/
private final static Set<String> ALLOW_DOMAINS = new HashSet<>();
static {
ALLOW_DOMAINS.add("http://127.0.0.1:8848");
}
@Override
public void init(FilterConfig config) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
// 獲取客戶端原始請求域
String origin = request.getHeader("Origin");
String originDomain = removeHttp(origin);
if(ALLOW_DOMAINS.contains(originDomain)){
// 在響應物件中,新增CROS協議相關的header屬性
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,HEAD,PUT,PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,Authorization,authorization");
response.setHeader("Access-Control-Allow-Credentials","true");
}
//繼續往下傳遞
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {}
/**
* 移除http協議頭部
* @param url
* @return
*/
public static String removeHttp(String url){
return url.replace("http://", "").replace("https://", "");
}
}
接著,將其註冊到Servlet
容器中,示例如下:
@Configuration
public class FilterConfig {
/**
* 新增CrossFilter過濾器
* @return
*/
@Bean
public FilterRegistrationBean crossFilterBean() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setName("crossFilter"); // 指定過濾器名稱
registration.setFilter(new CrossFilter()); // 指定過濾器實現類
registration.setUrlPatterns(Collections.singleton("/*"));// 指定攔截路徑
registration.setOrder(1);// 指定順序
return registration;
}
}
最後,啟動服務後,再到瀏覽器中發起跨域請求,看看效果如下。
從結果上看,瀏覽器成功進行了跨域請求,並展示了伺服器返回的結果。
2.2、方法二:透過全域性配置類實現跨域訪問
在 Spring Boot 應用,除了採用過濾器的方式實現跨域訪問外,我們還可以透過全域性配置類實現跨域訪問。
實現方法也非常簡單,只需要重寫WebMvcConfigurer
介面中的addCorsMappings
方法即可,示例如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.maxAge(3600)
.allowedHeaders("Origin", "Accept", "Content-Type", "Authorization")
.allowCredentials(true);
}
}
其中allowedOrigins("*")
表示對所有請求都允許跨域訪問。
2.3、方法三:透過CrossOrigin註解實現跨域訪問
某些場景,如果不希望所有的介面都能跨域訪問,只想在部分介面上放開跨域訪問。此時,可以透過 Spring Boot 提供的@CrossOrigin
註解,在對應的方法上加上該註解,即可實現跨域訪問。
示例如下:
@RestController
public class UserController {
@Autowired
private UserService userService;
@CrossOrigin
@PostMapping(value = "/queryAll")
public List<User> queryAll(){
List<User> result = userService.queryAll();
return result;
}
}
如果使用在controller
類上,表示當前類下的所有介面方法都允許跨域訪問。
同時,@CrossOrigin
註解也支援設定更小的粒度,示例如下:
@CrossOrigin(origins = "http://domain.com", maxAge = 1800)
更多的屬性行為,內容如下:
- origins: 允許的源列表,多個源可以使用逗號分隔
- methods: 允許的 HTTP 方法列表
- allowedHeaders: 允許的請求頭列表,預設情況下,允許所有請求頭
- allowCredentials:設定是否允許攜帶憑證
- maxAge: 預檢請求的快取時間(以秒為單位)
03、小結
最後總結一下,在 Spring Boot 服務中可以透過過濾器或者配置類實現全域性跨域訪問,也可以透過@CrossOrigin
註解實現區域性跨域訪問。
跨域訪問的配置,更適合在開發環境中方便前後端進行聯調對接。為了安全起見,在上生產的時候,建議將其關閉掉或者做限制。
此外,想要獲取專案原始碼的小夥伴,可以點選:跨域請求,即可獲取取專案的原始碼。
04、參考
1.https://cloud.tencent.com/developer/article/1655583
2.https://cloud.tencent.com/developer/article/1924258