1. 程式人生 > >spring boot專案實戰:跨域問題解決

spring boot專案實戰:跨域問題解決

背景

前後端分離架構,前端anglerjs,後端spring boot,使用shiro作為許可權控制,已配置通用跨域請求支援。
前端呼叫介面時部分情況正常,部分情況出現跨域請求不支援情況,錯誤資訊如下:

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'xxxx' is therefore not allowed access.

配置錯了?

首先,想到的就是對跨域請求支援的配置是錯誤的,嘗試著替換不同的跨域支援配置,有以下幾種:
1、繼承WebMvcConfigurerAdapter
@Configuration
public class AppConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true).allowedHeaders("Origin, X-Requested-With, Content-Type, Accept")
                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS")
                .maxAge(3600);
    }
}

2、配置WebMvcConfigurer

@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurerAdapter() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**").allowedOrigins("*")
                    .allowedMethods("*").allowedHeaders("*")
                    .allowCredentials(true)
                    .exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L);
        }
    };
}

If you are using Spring Security, make sure to enable CORS at Spring Security level as well to allow it to leverage the configuration defined at Spring MVC level.

大概意思就是使用Spring Security要進行特殊的配置來支援CORS。而當前專案內使用的是shiro,是不是許可權控制導致的問題?檢查shiro相關程式碼,果然找到了問題,在loginFilter內會判斷如果未登入,就通過response寫回未登入提示,程式碼如下:

Subject subject = SecurityUtils.getSubject();
if (!subject.isAuthenticated()) {
    HttpServletResponse resp = (HttpServletResponse) response;
    String contentType = "application/json";
    resp.setContentType(contentType);
    resp.setCharacterEncoding("UTF-8");

    Map<String, String> map = Maps.newLinkedHashMap();
    map.put("code", "xxx");
    map.put("msg", "xxx");
    String result = JacksonHelper.toJson(map);
    PrintWriter out = resp.getWriter();
    try{
        out.print(result);
        out.flush();
    } finally {
        out.close();
    }
    return;
}

那就新增上跨域支援

resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));

本來以為ok了,但是前端是不報錯了,但並不能獲得對應介面期望的結果,而是一直收到

{"code":"xxx","msg":"xxx"}

顯然是被登入攔截了,但是明明已經登入,而且有的介面可以正常通過登入攔截,為什麼部分介面會出現不能登入的情況呢?

明明登入了,為什麼被loginFilter攔截?

遇到了問題就要想辦法解決,首先就是懷疑客戶端sessionId未被正常儲存,在loginFilter內新增日誌列印sessionID,發現每次的sessionID都不一樣,問題清晰了一些,前端並未正確的保持登入狀態,對比前端兩個呼叫介面的程式碼,發現正常的是get請求,post請求不正常,通過在網上搜索,發現ajax post跨域請求時,預設是不攜帶瀏覽器的cookie的,也就是每次請求都會生成一個新的session,因此post請求都被登入攔截。解決辦法如下:

$.ajax({
    type:"POST",
    url:"",
    data:{},
    crossDomain:true,
    xhrFields: {  withCredentials: true  },
    success:function(data){

    },
    error:function(data){

    }
})

配置crossDomain:true 和 xhrFields: { withCredentials: true }就可以讓請求正常攜帶cookie。

一個完整可用方案

1、配置支援跨域請求(多種方式自由選擇,推薦使用下面的方式)

@Configuration
public class WebConfig {

    /**
     * 跨域請求支援
     */
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*")
                        .allowedMethods("*").allowedHeaders("*")
                        .allowCredentials(true)
                        .exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L);
            }
        };
    }
}

2、前端ajax post請求時新增xhrFields: { withCredentials: true }

$.ajax({
    type:"POST",
    url:"",
    data:{},
    crossDomain:true,
    xhrFields: {  withCredentials: true  },
    success:function(data){

    },
    error:function(data){

    }
})

3、檢查許可權控制程式碼,看是否有特殊處理的地方,未新增跨域支援。如上文所提,登入攔截直接通過response寫回未登入提示;
使用spring-security框架時也要新增特殊配置,如下:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()...
    }
}

解決跨域的本質就是在返回頭內新增Access-Control-Allow-Origin,實現方式有多種,spring體系內解決跨域可參考CORS support in Spring Framework,很全面的介紹了各種場景。使用許可權框架時,要注意許可權框架本身的CORS支援。