vue前后端分离 使用fetch 跨域请求时 session失效问题解决

前台是vue使用fetch请求后台的登录方法,但是前台浏览器的控制台中的sessionid没有,要么就是跟后台的sessionid不一致,导致后台取验证码的时候是null,因为验证码是后台存在session中
  在用fetch进行网络请求的时候,发现每次请求到服务端的时候,他的sessionId 都是不一样的,后面排查原来是在请求的时候fetch默认是不会带上本地jsessionId,以至于服务端无法接收到,所以会重新创建一个新的session。

针对这个问题,完整写法是:主要是 credentials: 'include' 是解决跨域session丢失的问题,加上这一行以后,前端浏览器中的sessionid和后台打印的sessionid是一致的,但是参数传不过来,最后加了一个method:'post',就可以了
                   let fd=new FormData();
                    fd.append('username',this.userName);
                    fd.append('password',this.userPwd);
                    fd.append('checkCode',this.code);
                    fetch(`${apiUrl}/login`,{
                         method:'post',
                        body:fd,
                        credentials: 'include'
                    }).then((res)=>res.json()).
                    then((res)=>{
                        console.log(res);
                    }).catch((res)=>console.log(res));

后台代码
(注:
        response.setHeader("Access-Control-Allow-Origin", " http://192.168.1.136:8080" ;);
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
        response.setHeader("Access-Control-Allow-Credentials", "true");//是否支持cookie跨域
后台什么都不需要加,根本 不需要加跨域这些东西

/**
* Created by HONGLINCHEN ON 2017/11/13 9:12
*  验证码
*  @param  request
*  @param  response
*  @return
*/
@GetMapping(value =  "/captchaImage")
public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response)  throws Exception {
response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin"));
//  禁止服务器端缓存
response.setDateHeader( "Expires"0);

//  设置标准的  HTTP/1.1 no-cache headers.
response.setHeader( "Cache-Control""no-store, no-cache, must-revalidate");

//  设置 IE 扩展  HTTP/1.1 no-cache headers (use addHeader).
response.addHeader( "Cache-Control""post-check=0, pre-check=0");

//  设置标准  HTTP/1.0  不缓存图片
response.setHeader( "Pragma""no-cache");

//  返回一个  jpeg  图片,默认是 text/html( 输出文档的 MIMI 类型 )
response.setContentType( "image/jpeg");

//  为图片创建文本
String capText =  captchaProducer.createText();

//  将文本保存在 session 中,这里就使用包中的静态变量吧
request.getSession().setAttribute(com.google.code.kaptcha.Constants. KAPTCHA_SESSION_KEY, capText);
//  创建带有文本的图片
BufferedImage bi =  captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
//  图片数据输出
ImageIO. write(bi,  "jpg", out);

String kaptchaExpected = (String) request.getSession().getAttribute(Constants. KAPTCHA_SESSION_KEY);
System. err.println( " 登录的验证码是: "+kaptchaExpected);
try {
out.flush();
finally {
out.close();
}
return null;
}

/**
*  验证验证码
*  @param  checkCode
*  @return  正确 :true/ 错误 :false
*/
public static boolean validate(String checkCode)  throws Exception {
//  获取 Session 中验证码
Object captcha = RequestUtils. getAttribute(com.google.code.kaptcha.Constants. KAPTCHA_SESSION_KEY);
if(captcha== null){
throw new Exception(UserReturnCodeEnum. INCALID_VALIDATION_CODE.getMessage());
}
if (StringUtils. isEmpty(checkCode)) {
return false;
}
boolean result = checkCode.equalsIgnoreCase(captcha.toString());
if (result) {
RequestUtils. getRequest().getSession().removeAttribute(com.google.code.kaptcha.Constants. KAPTCHA_SESSION_KEY);
}
return result;
}

/**
* Created by HONGLINCHEN ON 2017/11/13 9:12
*  用户登录
*  @param  username
*  @param  password
*  @return
*/
@PostMapping( "login")
@ResponseBody
public Object login( @RequestParam( "username")String username,  @RequestParam( "password")String password, @RequestParam( "checkCode")String checkCode,HttpServletRequest request,HttpServletResponse response)  throws Exception {
if (!SingletonLoginUtils. validate(checkCode)) {
return new GmfResult(UserReturnCodeEnum. VERIFICATION_ERROR);
}
Subject currentUser = SecurityUtils. getSubject();
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token =  new UsernamePasswordToken(username, password);
token.setRememberMe( true); //  默认不记住密码
try {
currentUser.login(token);
GmfUsersLoginLog usersLoginLog =  new GmfUsersLoginLog(SingletonLoginUtils. getUserId(), new Date(), RequestUtils. getIpAddr(RequestUtils. getRequest()),RequestUtils. getUserOperatingSystem(),RequestUtils. getUserBrowser());
Integer count =  gmfUsersLoginLogService.updateGmfUserLoginLogByUserId(SingletonLoginUtils. getUser(),usersLoginLog);
if (count> 0){
request.getSession().setAttribute(GmfUsersGlobal. USER_SESSION_KEY, currentUser.getPrincipal());
return ResultUtils. successLogin(ResultEnum. LOGIN_SUCCESS);
} else {
return ResultUtils. error(ResultEnum. UNKNOW_ERROR.getCode(),ResultEnum. UNKNOW_ERROR.getMsg());
}
catch (UnknownAccountException uae) {
logger.error(username+UserReturnCodeEnum. USER_NOT_EXIST.getMessage());
logger.info( "There is no user with username of " + token.getPrincipal());
return new GmfResult(UserReturnCodeEnum. USER_NOT_EXIST);
catch (IncorrectCredentialsException ice) {
logger.error(username+UserReturnCodeEnum. WRONG_PASSWORD.getMessage());
logger.info( "Password for account " + token.getPrincipal() +  " was incorrect!");
return new GmfResult(UserReturnCodeEnum. WRONG_PASSWORD);
catch (ExcessiveAttemptsException e) {
//  登录失败多次,账户锁定 5 分钟
logger.error(UserReturnCodeEnum. ACCOUNT_LOCK.getMessage());
return new GmfResult(UserReturnCodeEnum. ACCOUNT_LOCK);
catch (LockedAccountException lae) {
logger.error(UserReturnCodeEnum. USER_SUSPEND.getMessage());
return new GmfResult(UserReturnCodeEnum. USER_SUSPEND);
} catch (DisabledAccountException dae) {
logger.error(UserReturnCodeEnum. USER_DISABLE.getMessage());
return new GmfResult(UserReturnCodeEnum. USER_DISABLE);
} catch (AuthenticationException ae) {
return new Exception( " 登录失败! ");
catch (RuntimeException e) {
logger.error(UserReturnCodeEnum. UNKNOWN_ERROR.getMessage());
return new GmfResult(UserReturnCodeEnum. UNKNOWN_ERROR);
}
} else {
return ResultUtils. successLogin(ResultEnum. LOGIN_SUCCESS);
}
}

完结


说明:当时参考这个( https://www.cnblogs.com/neverleave/p/6533133.html)文章,但是我们前台加入 headers: {
'Content-Type': 'application/x-www-form-urlencoded' ,
},是不行的  参数传递不到后台,最后不加解决

javascript 使用fetch进行跨域请求时默认是不带cookie的,所以会造成 session失效。
fetch(url, {
method: 'POST' ,
credentials: 'include' ,
headers: {
'Content-Type': 'application/x-www-form-urlencoded' ,
},
body: JSON.stringify({
data: options.data
})
})
credentials: 'include' 可以是fetch 带上cookie。但是问题了来。
原来在服务器端设置header (php 服务器)
header ("Access-Control-Allow-Origin: *");
会报错:
A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin ' http://localhost:8000' ; is therefore not allowed access.
可以看到不允许 使用‘*’ 号了,那么就改成 访问域名(这里是本地调用所以是  http://localhost:8000
header("Access-Control-Allow-Origin: http://localhost:8000" ;);
改完后再次发送请求,还是报错
Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin ' http://localhost:8000' ; is therefore not allowed access.
说'Access-Control-Allow-Credentials 头必须是true,那么继续增加
header ("Access-Control-Allow-Credentials: true");
增加完后可以正常访问了,而且session也有了。
 
ps: fetch 有个mode 是no-cors ,发现设置后返回的status是0,查资料后
no-cors  mode is only to CDN content, such as scripts, CSS and image, you cannot be used for getting data, response.status = 0  is right behavior
no-cors 模式只能用来获取CDN内容,比如脚本,css文件和图片,如果用来获取数据比如json格式就会返回status=0



还参考了这篇资料:
http://blog.csdn.net/codetomylaw/article/details/52588493

《React-Native系列》31、 Fetch发送POST请求的坑与解决方案

我们在请求http接口时候,通常都会使用get和post的方式,针对表单提交这类的请求,我们通常采用post方式。

那么在RN中的Fetch API中post提交有哪些坑呢?让我们撸起来。


我们先来说说Server端的代码,通常我们从Request获取参数时的方法为:
[java]   view plain   copy
  1. String paraValue = request.getParameter(paraName);  
我们下面说的判断能不能获取参数,就是按照这种方法来获取。

在 RN中,通常我们会怎么写代码呢?
方案一(不推荐)
[javascript]   view plain   copy
  1. let url = "http://127.0.0.1:8080/api/testFetch”  
  2. let params = "name=admin&password=admin123”;  
  3. fetch(url , {  
  4.   method: 'POST',  
  5.   headers: {},  
  6.   body: params,  
  7. }).then((response) => {  
  8.   if (response.ok) {  
  9.       return response.json();  
  10.   }  
  11. }).then((json) => {  
  12.   alert(JSON.stringify(json));  
  13. }).catch((error) => {  
  14.   console.error(error);  
  15. });  

此时我们发现在Server端无法获取到name和password的值。
换成GET试试,将params追加到url后,发现ok。那这是什么情况呢?下面讲解。

好,不行,我们就再换一种方法试试呗。
方案二(不推荐):
[javascript]   view plain   copy
  1. let params = {"name":"admin","password":"admin123"};  
  2.   
  3. fetch(url , {  
  4.   method: 'POST',  
  5.   headers: {},  
  6.   body:JSON.stringify(params),  
  7. }).then((response) => {  
  8.   if (response.ok) {  
  9.       return response.json();  
  10.   }  
  11. }).then((json) => {  
  12.   alert(JSON.stringify(json));  
  13. }).catch((error) => {  
  14.   console.error(error);  
  15. });  
我们直接将params封装成一个JSON对象,然后在body里将JSON对象转成字符串传过去,发现然并卵,Server端还是获取不到值。


好,我们不兜圈子了,直接说明原因。
其实,方案一和方案二都是直接在body里传递了一个字符串,在Server端获取body的方式如下:
[java]   view plain   copy
  1. StringBuilder buffer = new StringBuilder();  
  2. BufferedReader reader = beat.getRequest().getReader();  
  3. String line;  
  4.  while ((line = reader.readLine()) != null) {  
  5.     buffer.append(line);  
  6.  }  
  7. String body = buffer.toString();  
通过这种方法我们可以获取到传入的字符串。
既然能获取到字符串,那么我们也可以拿到我们传入的值了,可以转JSON、或者按&切割字符串,只不过这种解决方案好像有点挫啊!!!

也许你会问在jquery中,我们就是按照方案一这种方式做的啊,怎么好使呢?
答:因为参数 "name=admin&password=admin123” 在jquery中,传入对象框架会自动封装成formData的形式,fetch没有这个功能。

终极方案(推荐使用):

既然fetch不会自动转FormData,那我们自己new一个FormData,直接传给body。
在FormData中也可以传递字节流实现上传图片的功能。参考: http://blog.csdn.net/codetomylaw/article/details/52446786
[javascript]   view plain   copy
  1. let formData = new FormData();  
  2. formData.append("name","admin");  
  3. formData.append("password","admin123");  
  4.   
  5. etch(url , {  
  6.  method: 'POST',  
  7.  headers: {},  
  8.  body: formData,  
  9. ).then((response) => {  
  10.  if (response.ok) {  
  11.      return response.json();  
  12.  }  
  13. ).then((json) => {  
  14.  alert(JSON.stringify(json));  
  15. ).catch((error) => {  
  16.  console.error(error);  
  17. );  
这样我们就可以在Server端获取到name和password的值了。

你可能感兴趣的:(vue前后端分离 使用fetch 跨域请求时 session失效问题解决)