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請求,以便讓前端不會誤認為請求地址不可達。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM