關於springboot集成shiro後遇到的CORS跨域問題

廢話不多說,先上解決辦法,後邊再說原理:

自定義MyFormAuthenticationFilter

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
    /**
     * 在訪問controller前判斷是否登錄,返回json,不進行重定向。
     * @param request
     * @param response
     * @return true-繼續往下執行,false-該filter過濾器已經處理,不繼續執行其他過濾器
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        if (isAjax(request)) {
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json");
            //解決一下跨域問題
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
            httpServletResponse.setHeader("Access-Control-Max-Age", "0");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpServletResponse.setHeader("XDomainRequestAllowed","1");
            httpServletResponse.getWriter().write(BaseRestfulResult.needLogin().toJsonString());
            httpServletResponse.getWriter().flush();
            httpServletResponse.getWriter().close();
        } else {
            //saveRequestAndRedirectToLogin(request, response);
            /**
             * @Mark 非ajax請求重定向爲登錄頁面
             */
            httpServletResponse.sendRedirect("/login");
        }
        return false;
    }

	//解決OPTIONS請求跨域問題
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (request instanceof HttpServletRequest) {
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
                return true;
            }
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }


    private boolean isAjax(ServletRequest request){
        String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
        if("XMLHttpRequest".equalsIgnoreCase(header)){
            return Boolean.TRUE;
       }
       return Boolean.FALSE;
        return true;
    }
}

注意上邊的isAccessAllowed()方法和onAccessDenied()方法中的幾行httpServletResponse.setHeader(“xx”, “xxx”);,這兩個地方是關鍵。
最後別忘了在config類中註冊一下
shiroFilterFactoryBean.getFilters().put(“authc”, new MyFormAuthenticationFilter());
大功告成~

以下是原理

最近在用springboot寫前後端分離項目的時候, 用postman測試接口沒問題,但丟給前端測試的時候返回了跨域問題:
在這裏插入圖片描述
這就很奇怪,所有的controller上我都加了@CrossOrigin註解,爲什麼還會有跨域問題呢?

這裏有個需要注意的地方: xx has been blocked by CORS policy
這個CORS是個什麼東西呢?簡單說是一種資源共享機制。當瀏覽器發起ajax請求的時候,會先發起一個method爲OPTIONS的請求, 這個請求我們可以簡單理解爲一個探路請求, 該請求不攜帶信息, 只是爲了測試一下目標服務器是否支持跨域,如果支持跨域的話,再發出後續的請求。
在這裏插入圖片描述
通過chrome控制檯可以看出, 當我們嘗試調用listByAdmin接口的時候,發出了一個OPTIONS請求,該請求返回200後,瀏覽器又發起了一個一模一樣的請求,後邊這個請求才是真正的請求,當然這是正常的情況。
問題就出在這個OPTIONS請求上, 因爲我的項目是完全前後端分離的,前端請求的時候會在http header上攜帶token,後端利用token鑑權。然而OPTIONS請求(‘OPTIONS請求’這個叫法可能不準確,各位理解我說的是啥就行)默認是不攜帶token信息的,後端沒有收到token,導致鑑權失敗。

但是鑑權失敗爲什麼報鑑權問題,反而報了個跨域問題呢?
先看一下一開始我是怎麼處理鑑權失敗的

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
   @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        if (isAjax(request)) {
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json");
            //返回給前端鑑權失敗的信息
            httpServletResponse.getWriter().write(BaseRestfulResult.needLogin().toJsonString());	
            httpServletResponse.getWriter().flush();
            httpServletResponse.getWriter().close();
        } else {
            //saveRequestAndRedirectToLogin(request, response);
            /**
             * @Mark 非ajax請求重定向爲登錄頁面
             */
            httpServletResponse.sendRedirect("/login");
        }
        return false;
    }
    }

首先判斷是否ajax請求,如果是的話,將鑑權失敗的信息返回給前端。 這個地方沒有設置允許跨域的header,所以前端就出現了跨域問題。

解決之後的源碼:

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        if (isAjax(request)) {
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json");
            //解決一下跨域問題
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
            httpServletResponse.setHeader("Access-Control-Max-Age", "0");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpServletResponse.setHeader("XDomainRequestAllowed","1");
            httpServletResponse.getWriter().write(BaseRestfulResult.needLogin().toJsonString());
            httpServletResponse.getWriter().flush();
            httpServletResponse.getWriter().close();
        } else {
            //saveRequestAndRedirectToLogin(request, response);
            /**
             * @Mark 非ajax請求重定向爲登錄頁面
             */
            httpServletResponse.sendRedirect("/login");
        }
        return false;
    }

當然,最佳的解決方案是針對OPTIONS請求單獨處理以下

   @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (request instanceof HttpServletRequest) {
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
                return true;
            }
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章