前言
第一次正儿八经的写博客实在有点不知道怎么开头好,所学的东西也不够系统,我相信我写的东西瞄准了一个点去写,无论从哪里开始,都会让人觉得有点突然,但是,我也没办法从所谈主题的起源开始谈,所以不纠结这个次序关系了,有关主题的前后我就稍微介绍一些,主要围绕我所遇到的问题和如何去解决来谈吧。
我使用的框架是springboot+angularjs ,内容主要包括:
- 为什么需要跨域
- js跨域请求
- 使用localStorage替代本地Cookies
- 使用token替代跨域发送Cookies
下面主要针对以上几点来分别说说
为什么需要跨域
我们这个俄项目是使用springboot+angularjs开发的,web端顺利开发结束,接下来是手机端,手机端直接使用html在电脑端运行也开发的比较顺利,因为是直接使用的PC端浏览器,所以和web端开发是没有区别的,当然,手机端的浏览器也同样不会有什么问题。但是我们公司项目能打包成android和ios的app,android和ios都支持webview,按理直接把url告诉webview然后去请求服务器访问也没什么问题,毕竟通过webview的形式,理论上就相当于使用了android和ios提供的一个浏览器而已,只是它隐藏了url。
考虑到手机上的切换效果,app开发的同事会使用多个webview来切换渲染数据,像详情之类的页面,需要开启新的webview,这样问题就来了,体验就非常的差了,非常的卡!非常的慢!为什么?
我简单的做了个比较.
Web端打开新页面过程
App端打开新页面过程
过程比较:
过程 | web | app |
---|---|---|
第一次加载 | 慢 | 慢 |
切换页面 | 快 | 慢 |
由此可见,使用android或ios嵌套webview来达到一般原生的切换效果和访问服务器的速度,是很难的!
此时我们想到三个方案:
app端只开启一个webview。
-
- 优点:稍微加快静态页面访问速度。
- 缺点:提升速度不明显。
虽然是想到了三个可行方案,但第一种方案直接被pass,第二种方案速度提升非常不明显,那只能考虑采用第三种了,将原先html文件与java文件共同打包成一个war包,然后现在需要将html文件直接打包进app文件,必然就需要js进行跨域请求了。
JS跨域请求
s跨域请求需要在js和服务器端都有申明跨域,具体如下:
- 原生ajax 请求可以这样写:
$.ajax({
type: 'POST',url: "/user",data: {
'phone': 'xxxxxxxx','password':'xxxxxxx'
},withCredentials: true,// 跨域
dataType: 'json'
}).success(function(){
});
- angularjs
$http.get(url,{
withCredentials : true //跨域
});
- java过滤器代码
@Component
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException,IOException {
response.setHeader("Access-Control-Allow-Origin",request.getHeader("origin")); //允许跨域
response.setHeader("Access-Control-Allow-Credentials","true");
if ("OPTIONS".equals(request.getMethod())) {
response.setHeader("Access-Control-Allow-Methods","POST,GET,PUT,OPTIONS,DELETE");
response.setHeader("Access-Control-Max-Age","3600");
response.setHeader("Access-Control-Allow-Headers","Origin,X-Requested-With,Content-Type,Accept,Authorization");
}
filterChain.doFilter(request,response);
}
}
response.setHeader("Access-Control-Allow-Origin",request.getHeader("origin"));
这行代码是有点意思的,我这里没有将值直接设为“*”号,将值设为星号无法跨域发送cookies,需要发送cookies的可以设置成
request.getHeader("origin");
这样跨域发送请求就没有问题了。
使用localStorage替代本地Cookies
跨域后有个问题真的让我头疼了很久,虽然成功跨域了,数据也都已经成功返回并渲染页面了。但依然有几个问题。
- 登录问题
比如,登录成功后session在后台保存了用户会话信息,再次发送请求获取数据时,再次读取session验证用户身份时,始终无法取到登录时存储的会话信息,因为没有获取到cookies,而服务器与浏览器之间的会话sessionID是保存在cookies中的,为什么没有获取到cookies,是因为服务器端允许跨域是这样设置的:
response.setHeader("Access-Control-Allow-Origin","*"); //允许跨域
origin设置成“*”,是不支持文件形式的访问所发送cookies的,需要怎么改呢?改成与本地文件一样的路径形式就成,比如,在我的机器上如果html路径是 file:///D:test.html,我在java端允许跨域就必须设置成
response.setHeader("Access-Control-Allow-Origin","file://"); //允许跨域
才能获取到cookies,考虑到html打包进ios或android app中的路径可能不同,则将路径设为:
response.setHeader("Access-Control-Allow-Origin",request.getHeader("origin"));
这点可让我找了很久,“*”居然不行,我到现在也没想通。
- 本地cookies保存
登录的问题解决了,发送cookies也没有问题了,但是本地文件的cookies保存又出了问题,无法保存!也许有办法只是我没找到。
考虑到不同浏览器之间的差别和ios对cookies的可能支持不太好的问题,决定打算使用用localStorage在本地文件保存一些信息,因为localStorage是html5自带的,不存在不同浏览器之间的差别。
localStorage的使用也比较简单,我在这个项目中也只使用了几个方法。
首先申明一个全局的storage,因为本身用他的目的就是替代cookies保存用户状态,当然是全局的啦
var storage = window.localStorage;
然后保存值
storage.setItem('userid','深蓝浅蓝的天')
取值
storage.getItem('userid');
退出清空storage
storage.clear();
so easy,我很快就搜索全部代码将cookies的使用全部替换掉了(注意搜索路径哦),但是问题到了这里还是没有结束,在电脑上测试完了本地文件的访问都没有了问题,但是用android或ios打包html文件又出现问题了。
post方法可正常跨域,因为它的headers中的origin是有指的,而get方法origin却是是null,而服务器端对于null是不接收跨域发送cookies,所以cookies还是要忍痛替换掉。++
使用token替代跨域发送Cookies
cookies既然有那么多的问题,而且可能在某些地方支持不太好,之后若是需要对app进行功能扩展,第三方授权之类的,也还是要走token,所以索性直接换掉cookies,当然,我在js中是有区分web/ios/android的,所以手机浏览器端仍然延续使用cookies,ios和android打包文件形式则用token。
虽然在服务器代码中session几乎无处不在,但要替换成token也不会特别麻烦,将原先保存在session中的数据,使用token作为一个键保存在redis中,然后在用户请求服务器时,拦截请求并取出token,再从redis中取出数据手动赋值到session中就可以了,只是我把原先服务器接收请求并取出cookies中sessionID,从而取出对应session的动作替换了下而已。
简单图示就是:
- 服务器处理cookies
第一次请求服务器
第二次请求服务器
大致过程如上图,用markdown不会画图,下次画个好点的~~
- token替换cookie
第一次请求服务器
第二次请求服务器
过程几乎是一样的,只是我在拦截中做了一步处理,以便我不需要大幅度改变原有代码,如下:
@Override
public boolean preHandle(HttpServletRequest request,Object handler) throws Exception {
HttpSession session = request.getSession();
if (session.getAttribute("userId") == null) {
String token = request.getHeader("Authorization");
if (token != null && !token.equals("null")) {
String jsonToken = (String) redisTemplate.opsForValue().get(token);
UserToken userToken = JSON.parSEObject(jsonToken,UserToken.class);
if (userToken != null) {
session.setAttribute("userId",userToken.getUserId());
}
}
}
return true;
}
过程大致如此。
第一次写这么长的博文,欢迎指正。