好处:
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");