项目场景:
使用技术,后端srpingboot+security,前端Vue+elementui+axios问题描述:
security有两个强大功能之二:自动登录和防CSRF攻击。防CSRF攻击的功能说白了,就是多加一个token验证。然而这两个功能同时开启时,会出现POST/PUT请求无访问权限。原因分析:
这里的问题原因就是token,对于一次建立连接,CRFS的token是唯一的,但是自动登录跳过了登录环节,因此CRFS的token值是重新生成的,也就意味着之前保存的token无效了。因此只需要重新获取保存即可。这里遇到一个重大问题:就是当自动登录时,直接进入的主页面,但是在主页面下有多个异步请求获取数据,这就会多次发送token值。会出现时而有效时而无效的问题。
解决方案:
经过测试,如果登录时只发送一次请求,之后再发送其他异步请求,就能保证只有一个session会话。也就能保证只发送一个token。这样即可保证之后发送的异步请求使用的是同一个token。 因此这里需要多加一次请求,这个请求可以什么也不做,只保证必须第一次请求。因此最好放在App.vue的beforeCreate函数中。如下所示:export default {
name: 'app',
beforeCreate() {
// 这里的axios已封装在window中,因此能够保证调用的到
// 解决 免登陆 CRFS问题
var xmlhttp;
if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
} else { // code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
};
if (!xmlhttp) {
console.log("不支持xml发送")
return;
};
// 发送同步请求,保证成功之后再去请求其他异步请求
xmlhttp.open("get", process.env.VUE_APP_PROXYNAME + "/ok?t=" + Math.random(), false);
}
}
后端首先保证在登录成功后做一次token发送,保存在cookie中,保证不需要在前端有任何操作
/**
* 登录成功回调
*/
public class LoginSuccessResponse extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
/**
* 获取CSRF密钥
* "csrf": {
* "headerName": "X-CSRF-TOKEN", 必须是这个作为key
* "parameterName": "_csrf",
* "token": ""
* },
* 向前端发送密钥,每次请求写在头信息中,
* 例如 X-CSRF-TOKEN: 'token',即可保证正常访问
* 使用Cookie保证前端不需要做任何操作
*/
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
// 这里使用fastjson转成JSON格式
Cookie cookie = new Cookie("csrf", URLEncoder.encode(JSON.toJSONString(csrfToken),"UTF-8"));
// 保存10天,保证存活时间足够使用
cookie.setMaxAge(60 * 60 * 24 * 10);
cookie.setPath("/");
response.addCookie(cookie);
// sendCsrfToken记录boolean值,表示在一次浏览器会话中,保证发送一次toekn值
request.getSession().setAttribute("sendCsrfToken", true);
out.flush();
out.close();
}
}
接下来就是配置请求/ok,保证自动登录情况下也能正常访问
/**
* 公共请求
*/
@Controller
@Slf4j
public class CommonController {
/**
* 解决免登陆情况下CRFS验证问题
* @param request
* @param response
* @throws UnsupportedEncodingException
*/
@GetMapping("/ok")
@ResponseBody
public void ok(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
Object sendCsrfToken = request.getSession().getAttribute("sendCsrfToken");
if (sendCsrfToken == null || (boolean) sendCsrfToken == false) {
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
Cookie cookie = new Cookie("csrf", URLEncoder.encode(JSON.toJSONString(csrfToken),"UTF-8"));
cookie.setMaxAge(60 * 60 * 24 * 10);
cookie.setPath("/");
response.addCookie(cookie);
log.info("免登陆用户{},发送csrfToken:{}", SecurityContextHolder.getContext().getAuthentication().getName(),csrfToken.getToken());
request.getSession().setAttribute("sendCsrfToken", true);
}
}
}
配置之后即可保证在cookie中总会有一个有效的crsftoken值,只需要在每次请求拦截前配置请求头即可正常访问。如下:
_axios.interceptors.request.use(
function(config) {
let cookie = document.cookie;
if (cookie) {
// 这里使用vue-cookies插件,也可以直接从cookie中获取数据
let csrf = VueCookies.get("csrf ")
if (csrf) {
config.headers[csrf.headerName] = csrf['token'];
}
}
// Do something before request is sent
return config;
},
function(error) {
// Do something with request error
return Promise.reject(error);
}
);