前言:先後端分離,業務分離,網關路由等已經成爲當下web application開發的流行趨勢。前端以單頁面路由爲核心的框架爲主體,能夠單獨部署在nodejs或nginx上。後端以springboot爲表明的分佈式微服務框架爲主體,能夠獨立運行在任何端口上。相互經過符合restful規範的接口訪問或數據交換。在這樣的開發模式下,首先須要解決的就是因爲跨域而引發的訪問,cookie傳遞以及權限管理問題。本文以時下最流行的Angular2,Springboot,Zuul,Shiro爲例,提供最佳實踐。前端
1、通常訪問node
開發中首先遇到的問題就是因爲前端運行端口和後端運行端口不一致所形成的跨域訪問。推薦在Springboot項目上增長過濾器:nginx
@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 } }
2、身份認證authweb
當用戶登陸之後理論上咱們能夠在後端咱們能夠獲取當前用戶的session數據,但是依然存在問題。形成這個問題的主要緣由是後端依靠sessionid來確認數據,正常狀況下瀏覽器會將數據保存在cookie中,而在非跨域的環境中,瀏覽器的每一次訪問都會攜帶本身的cookie信息。可是Angular2這樣的異步框架http模塊默認請求時不會攜帶cookie信息的。這個問題的解決方法是前端請求須要增長「withCredentials: true」字段:spring
httpOptions: { headers: new HttpHeaders({ 'Content-Type': 'application/json' }), withCredentials: true } this.http.get<Users>(global.loginUrl + 'getUsers', global.httpOptions);
僅僅如此還不夠,若是咱們的前端並非直接訪問後端服務,而是經過Zuul這樣的網管路由作了代理訪問。你會發現後端依然沒法正確獲取session。緣由是Zuul默認是不轉發請求頭或會過濾掉一些重要的頭信息。所以咱們還須要在Zuul的配置文件中增長一條:json
zuul: routes: <servername> sensitiveHeaders: "*"
這樣前端請求攜帶的cookie信息纔會順利被髮送到後端服務,後端纔可以獲取到正確的session。後端
3、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也爲咱們預留了接口,咱們只須要按照需求配置便可。安全
4、總結
當下流行的先後端分離框架跨域是咱們遇到的第一個問題,咱們能夠在服務端配置容許跨域訪問來解決,具體方法推薦使用WebFilter過濾器。可是默認的rest請求sessionid,所以咱們還須要讓前端在請求頭攜帶cookie。另外若是咱們在前端展現與後端服務以前使用了網關還須要讓網關服務容許全部的請求頭經過而不要進行過濾。最後若是咱們還使用了安全框架就必須讓基於url權限管理功能放行全部的Options請求,以便讓前端不會誤認爲請求地址不可達。