文章分为三部分:
一:前后端分离跨域session不变问题(在不使用缓存数据库 如:redis 的情况下保证session不变)
二:Ajax跨域的同时传递身份信息
三:跨域情况下返回前台正常,但是ajax一直进入error函数
在不使用redis做session缓存的情况下,如何发送验证码呢?同时保证在一定时间内用户注册时sessionid不会改变?
我在一个注册发送验证码时,需要将验证码存储到session中,但是在跨域请求情况下,前台的ajax必须传递身份信息才能保证每次请求方法时的sessionid是一样的,否则在验证时永远也不找不到session中存储的验证码,下面是我的发送邮件的Controller层
/**
* 发送验证码
* @return 发送成功返回Constant.CODE_YES 失败返回Constant.CODE_NO
*/
@CrossOrigin
// @RequestMapping(value="sendIdentityCode/{email}", method = RequestMethod.GET)
// public String sendIdentityCode(@PathVariable String email){
@RequestMapping("sendIdentityCode")
@ResponseBody
public String sendIdentityCode(
@RequestParam("email") String email, HttpServletResponse response){
super.setResponseOrigin(request,response);
System.out.println("请求"+request.getSession().getId());
String res=sendEmailServiceImpl.sendEmail(Constant.MY_163EMAIL,email);
if(!(Constant.CODE_NO).equals(res)){
HttpSession httpSession=request.getSession();
//session 5分钟后删除验证码
//request.getSession().setAttribute(Constant.IDENTITY_CODE, res); 此方法错误
httpSession.setAttribute(Constant.IDENTITY_CODE, res);
try {
//TimerTask实现5分钟后从session中删除验证码Constant.IDENTITY_CODE
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(httpSession.getAttribute(Constant.IDENTITY_CODE));
httpSession.removeAttribute(Constant.IDENTITY_CODE);
System.out.println("验证码删除成功..");
timer.cancel();
}}, 15 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
return Constant.CODE_YES;
}
return Constant.CODE_NO;
}
这个方法很多地方你不必在意,重要的是http是无状态请求,当响应完成后,返回到前台页面后,request就重置为null了,所以这个时候你需要将你的验证码 存储到session中:
httpSession.setAttribute(Constant.IDENTITY_CODE, res); 这个方法是将验证码存储到session中
super.setResponseOrigin(request,response); 这个方法是我在超类中写的一个方法,设置响应内容的,后面介绍他的作用
httpSession.removeAttribute(Constant.IDENTITY_CODE); 在timerTask中删除之前发送验证码存储在session中的验证码,这一步很重要,因为在没有session缓存数据库redis,或者说在没有做session共享的情况下,如何保证在注册时用户点击注册请求注册方法是的sessionid也是这个sessionid呢?这就要涉及到第二部分了。
需要在ajax的请求中设置响应的参数,如下
function get_Identity_Code(){
var email=$("#r_email").val();
$.ajax({
url:"http://ip:端口/项目名/login/sendIdentityCode",
type:"GET",
dataType:"json",
data:{"email":email},
//processData: true, //对象型要指定 序列化 true非序列化
//contentType : false, //对象型要指定
//支持跨域
async:true,
timeout : 60000,
xhrFields: {
withCredentials: true
},
crossDomain: true,
error:function(error){
console.log("发送失败error");
},
// error: function(XMLHttpRequest, textStatus, errorThrown) {
// alert(XMLHttpRequest.status);
// alert(XMLHttpRequest.readyState);
// alert(textStatus);
// },
success:function(result){
if(result=='200'){
$("#r_email").css("color","lawngreen");
console.log("发送成功!");
}else{
$("#r_email").css("color","red");
$("#r_email").text("邮箱已被注册,你个大傻子");
console.log("发送失败!");
shake();
}
}
});
重要的是
xhrFields: {
withCredentials: true
},
crossDomain: true,
这时你会发现,返回前台数据业也取到可,状态也是200,但是ajax函数却一直进入error函数,参见第三部分
这时会办一个错误
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'
摘抄了一些解释:来自:https://www.jianshu.com/p/c115e4a24977
当请求的凭据模式为include时,相应中的Access-Control-Allow-Origin标头的值不能是通配符 "*"
如在请求定义中设置withCredentials标志,则会在请求中传递cookie等。
如果服务器返回任何set-cookie响应头, 那么必须返回Access-Control-Allow-Credentials: true, 否则将不会在客户端上创建 cookie
如果你这样设置,你需要同时指定了确切的Access-Control-Allow-Origin响应头,因为Access-Control-Allow-Origin: 不具有凭证兼容
--->当请求中携带cookie时, Access-Control-Allow-Origin必须要有确切的指定, 不能是通配符(*), 而withCredentials是跨域安全策略的一个东西
附加:
也就是说Access-Control-Allow-Credentials
设置为true
的情况下Access-Control-Allow-Origin
不能设置为*
ps: 关于指定域名 可以在后端用个array
类似的存一个白名单域名列表
如果有请求 先判断 Origin
是否在白名单里 然后再动态设置 Access-Control-Allow-Origin
最草根的解决办法,我们最喜欢:
在返回之前,设置response
这就是我的super方法:
/**
* 解决在没有缓存数据库情况下的session问题
* @param request
* @param response
*/
protected void setResponseOrigin(HttpServletRequest request,HttpServletResponse response){
response.setHeader("Access-Control-Allow-Credentials", "true"); //传递身份信息
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "POST,PUT,GET");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept");
}
当然这部分内容也可以写在你的过滤器或者拦截器中