好處:
1、讓前后端的開發人員可以更方便的開發和調試,各自專注於處理自己領域的問題;前端開發人員不需要知道后台使用什么技術實現,前后台通信都是基於Rest接口,像是兩個獨立的系統;
2、前端程序都是屬於靜態資源文件,單獨部署,有利於提高訪問性能;從Nginx訪問靜態資源比從Tomcat訪問要更快速;
3、在針對靜態資源文件做CDN優化時,獨立部署一台靜態資源服務器即可,CDN回源不會對應用程序服務器造成影響。
解決安全驗證問題
后端是基於Shiro的安全驗證框架
前端通過AJAX訪問后端Rest接口時,如果后端進行安全驗證時,在沒有驗證通過的情況下,給返回:
{ error : 'NOT_AUTHENTICATED', loginUrl : '{url}' }
前端要有一個針對AJAX請求的全局處理,來檢測該情況。
axios.interceptors.response.use(function (response) { if (response.data.error === 'NOT_AUTHENTICATED') { redirect(response.data.loginUrl) return Promise.reject(response.data.error) } return response }, function (error) { return Promise.reject(error) })
重定向到登錄頁面去驗證,需要傳一個backUrl參數,以便於完成登錄驗證后能跳轉回原來的頁面。
如何來處理這個backUrl呢?有兩種方案:
1、前端在發出AJAX請求時,將當前頁面地址(包括路由地址)發送給服務端,由服務端來完成整個loginUrl的裝配工作。
axios.interceptors.request.use(function (config) { config.headers.hash = window.location.hash return config }, function (error) { return Promise.reject(error) })
服務端要獲取當前客戶端頁面的完整地址(包括路由地址)就是通過下面的代碼:
// 獲取發送請求的來源網頁地址 String sourceUrl = request.getHeader("referer"); if (StringUtils.isNotEmpty(sourceUrl)) { sourceUrl += request.getHeader("hash"); sourceUrl = URLEncoder.encode(sourceUrl, "utf-8"); }
這里是在request的head部分增加了個一項hash,但是這個自定義的head項目在跨域訪問時又不被允許。
在跨域訪問的情況下,默認客戶端會發送兩次請求,第一次是探查,Method=OPTION,此時服務端要設置所允許的head項:
response.setHeader("Access-Control-Allow-Headers", "accept,content-type,hash");
2、另一種方式就是服務端下發loginUrl,里面帶有一個占位符,比如“${hash}”,到達客戶端后,由客戶端去替換成當前頁面的完整地址。
這樣就不需要自定義request的head項了。
當跳轉到一個loginUrl進行登錄驗證后,如果驗證通過了建立了Session,這個Session如何跟靜態資源網站共享呢?
為了在應用程序和前端靜態頁面之間共享Session,需要把Shiro的SessionCookie保存在根域名之下(動靜兩個應用必須是相同的根域名)。
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="sid"/> <property name="httpOnly" value="false"/> <property name="maxAge" value="-1"/> <property name="domain" value=".6655.la"/> </bean>
當設置到這一步時,又會發現跨域情況下,向服務端發送請求,Cookie是不會攜帶的。
必須在第一次發送OPTION探查時,要明確告訴客戶端允許攜帶Cookie
response.setHeader("Access-Control-Allow-Credentials", "true");