1. 程式人生 > >Angular2,Springboot,Zuul,Shiro跨域CORS請求踩坑實錄

Angular2,Springboot,Zuul,Shiro跨域CORS請求踩坑實錄

前言:前後端分離,業務分離,閘道器路由等已經成為當下web application開發的流行趨勢。前端以單頁面路由為核心的框架為主體,可以單獨部署在nodejs或nginx上。後端以springboot為代表的分散式微服務框架為主體,可以獨立執行在任何埠上。相互通過符合restful規範的介面訪問或資料交換。在這樣的開發模式下,首先需要解決的就是由於跨域而引起的訪問,cookie傳遞以及許可權管理問題。本文以時下最流行的Angular2,Springboot,Zuul,Shiro為例,提供最佳實踐。

一、一般訪問

開發中首先遇到的問題就是由於前端執行埠和後端執行埠不一致所造成的跨域訪問。推薦在Springboot專案上增加過濾器:

@WebFilter
public class CorsFilter implements Filter {
    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response 
= (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT"); response.setHeader(
"Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); response.setHeader("Access-Control-Allow-Credentials","true"); chain.doFilter(req, res); } @Override public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }

二、身份認證auth

當用戶登入以後理論上我們可以在後端我們可以獲取當前使用者的session資料,可是依然存在問題。造成這個問題的主要原因是後端依靠sessionid來確認資料,正常情況下瀏覽器會將資料儲存在cookie中,而在非跨域的環境中,瀏覽器的每一次訪問都會攜帶自己的cookie資訊。但是Angular2這樣的非同步框架http模組預設請求時不會攜帶cookie資訊的。這個問題的解決方法是前端請求需要增加“withCredentials: true”欄位:

httpOptions: {
        headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
        withCredentials: true
}
this.http.get<Users>(global.loginUrl + 'getUsers', global.httpOptions);

僅僅如此還不夠,如果我們的前端並不是直接訪問後端服務,而是通過Zuul這樣的網管路由做了代理訪問。你會發現後端依然無法正確獲取session。原因是Zuul預設是不轉發請求頭或會過濾掉一些重要的頭資訊。因此我們還需要在Zuul的配置檔案中增加一條:

zuul:
  routes:
    <servername>
      sensitiveHeaders: "*"

這樣前端請求攜帶的cookie資訊才會順利被髮送到後端服務,後端才能夠獲取到正確的session。

三、Shiro過濾器

準確的這個問題不是跨域造成的,根據目前HTML5的流行趨勢,在傳送Post和Put請求前會首先通過Options請求做探測。在沒有配置Shiro過濾器的情況下,這都不是問題。可以一旦後端服務配置了根絕url的許可權過濾,前端的請求又會出現問題。原因是Shiro的預設過濾器使用了重定向,而Options在重定向後認為地址不可達因此不會繼續傳送正確的請求。要解決這個問題,我們需要重寫Shiro的FormAuthenticationFilter:

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, @Qualifier("authFilter") Filter filter) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filters = new HashMap<>();
        filters.put("authc", filter);
        filters.put("roles", filter);
        filters.put("perms", filter);
        shiroFilterFactoryBean.setFilters(filters);
        
        Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
        
        filterChainDefinitionMap.put("/*", "anon");
        filterChainDefinitionMap.put("/auth/*", "authc");
        filterChainDefinitionMap.put("/auth/admin/*", "roles[admin,administrator]");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    
    @Bean("authFilter")
    public Filter authenticationFilter() {
        return new FormAuthenticationFilter() {
            @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);
            }

            @Override
            protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
                return false;
            }
        };
    }
}

當然Shiro也為我們預留了介面,我們只需要按照需求配置即可。

四、總結

當下流行的前後端分離框架跨域是我們遇到的第一個問題,我們可以在服務端配置允許跨域訪問來解決,具體方法推薦使用WebFilter過濾器。但是預設的rest請求sessionid,因此我們還需要讓前端在請求頭攜帶cookie。另外如果我們在前端展示與後端服務之前使用了閘道器還需要讓閘道器服務允許所有的請求頭通過而不要進行過濾。最後如果我們還使用了安全框架就必須讓基於url許可權管理功能放行所有的Options請求,以便讓前端不會誤認為請求地址不可達。