使用Spring Security中遇到的Preflight請求和跨域的問題

背景

在一個先後端分離開發的項目中,使用SpringSecurity作安全框架,用JWT來實現權限管理提高RESTful Api的安全性。首先遇到的就是跨域問題,可是在攜帶jwt請求過程當中出現了服務端獲取不到jwt狀況。html

跨域問題

在開發過程當中遇到CORS (跨域資源共享) 的問題,簡單的在服務器端設置了容許跨域訪問,可是在攜帶jwt請求過程當中出現
圖片描述
由於jwt是放在request header中,忽略了在跨域處理是加上容許本身定於的header字段前端

@Component
public class CorsFilter implements Filter {

    Logger logger= LoggerFactory.getLogger(CorsFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request= (HttpServletRequest) servletRequest;
        HttpServletResponse response= (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin",request.getHeader("origin"));
        response.setHeader("Access-Control-Allow-Origin","*");  //容許跨域訪問的域
        response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,DELETE,PUT");  //容許使用的請求方法
        response.setHeader("Access-Control-Expose-Headers","*");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Cache-Control,Pragma,Content-Type,Authorization");  //容許使用的請求方法
        response.setHeader("Access-Control-Allow-Credentials","true");//是否容許請求帶有驗證信息
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

在網上搜索提到要對OPTIONS請求進行處理返回200,可是測試並無起到效果
這裏的OPTIONS請求實際上就是preflight請求java

Preflight請求

可是問題依然沒有解決,出現以下
圖片描述
google了以後才知道preflight 請求的相關信息web

在咱們調用後臺接口的時候,常常會發現請求了兩次,其實第一次發送的就是preflight request(預檢請求)。spring

爲何須要preflight request

咱們都知道瀏覽器的同源策略,就是出於安全考慮,瀏覽器會限制從腳本發起的跨域HTTP請求,像XMLHttpRequest和Fetch都遵循同源策略。
瀏覽器限制跨域請求通常有兩種方式:數據庫

瀏覽器限制發起跨域請求 跨域請求能夠正常發起,可是返回的結果被瀏覽器攔截了
通常瀏覽器都是第二種方式限制跨域請求,那就是說請求已到達服務器,並有可能對數據庫裏的數據進行了操做,可是返回的結果被瀏覽器攔截了,那麼咱們就獲取不到返回結果,這是一次失敗的請求,可是可能對數據庫裏的數據產生了影響。json

爲了防止這種狀況的發生,規範要求,對這種可能對服務器數據產生反作用的HTTP請求方法,瀏覽器必須先使用OPTIONS方法發起一個預檢請求,從而獲知服務器是否容許該跨域請求:若是容許,就發送帶數據的真實請求;若是不容許,則阻止發送帶數據的真實請求。後端


瀏覽器將CORS請求分紅兩類:簡單請求和非簡單請求。api

簡單請求

    1. 請求方法是如下三種方法之一跨域

    • HEAD

    • GET

    • POST

    1. HTTP的頭信息不超出如下幾種字段

    • Accept

    • Accept-Language

    • Content-Language

    • Last-Event-ID

    • Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

    凡是不一樣時知足上面兩個條件,就屬於非簡單請求。
    而瀏覽器對這兩種請求的處理是不同的。

    非簡單請求

    非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
    非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight)

    與cors相關更詳細的看參考底部連接

    解決方法

    在咱們後臺用了Spring Security做爲安全框架,而且沒有對Preflight這個請求作出相應的處理,那麼這個請求會致使權限管控失敗。
    處理起來也很簡單,只須要在spring security配置類configure方法中增長放行preflight請求

    @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    // 因爲使用的是JWT,咱們這裏不須要csrf
                    .csrf().disable()
                    // 基於token,因此不須要session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests()
                    // 全部 / 的全部請求 都放行
                    .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()    //對preflight放行
                    .antMatchers("/*").permitAll()
                    .antMatchers("/u").denyAll()
                    .antMatchers("/article/**").permitAll()
                    .antMatchers("/video/**").permitAll()
                    .antMatchers("/api/**").permitAll()
                    .antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**","/swagger-ui.html", "/webjars/**")
                    .permitAll()
                    .antMatchers("/manage/**").hasRole("ADMIN") // 須要相應的角色才能訪問
                    // 除上面外的全部請求所有須要鑑權認證
                    .anyRequest().authenticated();
    
            // 禁用緩存
            http.headers().cacheControl();
            // 添加JWT filter
            http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
            //添加未受權處理
            http.exceptionHandling().authenticationEntryPoint(getAuthenticationEntryPoint());
            //權限不足處理
            http.exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
    
        }

    最終問題獲得解決!

    參考:
    前端 | 淺談preflight request
    跨域資源共享 CORS 詳解

    相關文章
    相關標籤/搜索