最近在做一个前后端分离的项目,前端使用React+Ant,后端是Spring mvc+mybatis+mysql,第一次做前后端分离的项目,又第一次用React+Ant,对于一直做后端的我来说,前端各种问题不断,项目时间又急,真是无比煎熬,但是又很享受在解决问题之后的喜悦。
问题描述:登录的时候前台axios跨域请求访问在java后台生成验证码并放到session中
/*
* 生成验证码
*/
@RequestMapping(value="/getVerifyCode",method={RequestMethod.POST,RequestMethod.GET})
public @ResponseBody String getVerifyCode(HttpServletRequest request, HttpServletResponse response){
//生成随机字串
String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
//存入会话session
HttpSession session = request.getSession();
//删除以前的
session.removeAttribute(Constants.VERIFY_CODE);
session.setAttribute(Constants.VERIFY_CODE, verifyCode.toUpperCase());
//生成图片
int w = 100, h = 40;
VerifyCodeUtils.outputImage(w, h, response.getOutputStream(), verifyCode);
return verifyCode;
}
然后在登录请求的时候去取,但是取到的session为null
HttpSession session = request.getSession(false);
//获取session中的验证码
String ssession_verCode=(String) session.getAttribute(Constants.VERIFY_CODE);
百思不得其解,各种百度,原来是因为跨域问题SSIONID每次请求都会变化,然而我在服务端返回时加入允许跨域的请求头,允许指定域名的跨域访问java后台,一个Filter代码如下:
import org.springframework.http.HttpHeaders;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 使用Filter的方式解决跨域问题
*/
public class CorsFilter implements Filter {
//private static final List ALLOW_ORIGINS = Config.getListString("allowOrigins", ",");
private static final String REQUEST_OPTIONS = "OPTIONS";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String orgHeader = request.getHeader(HttpHeaders.ORIGIN);
if (orgHeader != null ) {
// 允许的跨域的域名
response.addHeader("Access-Control-Allow-Origin", orgHeader);
// 允许携带 cookies 等认证信息
response.addHeader("Access-Control-Allow-Credentials", "true");
// 允许跨域的方法
response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT, HEAD");
// 允许跨域请求携带的请求头
response.addHeader("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With");
// 返回结果可以用于缓存的最长时间,单位是秒。-1表示禁用
response.addHeader("Access-Control-Max-Age", "3600");
// 跨域预检请求,直接返回
if (REQUEST_OPTIONS.equalsIgnoreCase(request.getMethod())) {
return;
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
并在web.xml中添加:
corsfilter
com.yhq.filter.CorsFilter
corsfilter
/*
REQUEST
同域安全策略CORS(Cross-Origin Resource Sharing) ,它要求请求的服务器在响应的报头(Response Header)添加 Access-Control-Allow-Origin标签,从而允许此标签所对应域访问此服务器的资源,调用此服务器的接口。
缺陷是:默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等),通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。如果服务器接收带凭据的请求,会用下面的HTTP头部来响应:
Access-Control-Allow-Credentials: true
如果发送的是带凭据的请求,但服务器的相应中没有包含这个头部,那么浏览器就不会把相应交给JavaScript,请求就无法得到结果的数据(浏览器得到了,但是我们请求的方法得不到,因为被浏览器拦截了),因此在需要传Cookie等时,服务端的Access-Control-Allow-Origin必须配置具体的具体的域名,并且还需要设置其他的请求头:
// 允许携带 cookies 等认证信息
response.addHeader("Access-Control-Allow-Credentials", "true");
// 允许跨域的方法
response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT, HEAD");
// 允许跨域请求携带的请求头
response.addHeader("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With");
对axios请求写一个工具类
import React from 'react'
import axios from 'axios'
import qs from 'qs'
axios.defaults.withCredentials = true //一定要配置
axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"
axios.defaults.timeout = 100000
/**
* get方法,对应get请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
function get(url, params){
return new Promise((resolve, reject) =>{
axios.get(url, {
params: params
}).then(res => {
resolve(res.data);
}).catch(err =>{
reject(err.data)
})
});
}
/**
* post方法,对应post请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
function post(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, qs.stringify(params))
.then(res => {
resolve(res.data);
})
.catch(err =>{
reject(err.data)
})
});
}
export default {
get,
post
};
以上操作后,则能正常跨域访问并且每次访问请求保证同一个sessionid。一个跨域问题牵涉出这么多问题出来,一一解决,解决完了,发现一个问题学习了好多知识点,所以问题才是最好的的老师。