ssm+vue 使用shiro后 post请求报错 Request header field Content-Type is not allowed by Access-Control-Allow-H
ssm+vue 使用shiro后 post请求报错 Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
出现的问题
当我发送post请求时,控制台就报错这个,很明显这个是跨域的问题。但是问题是我的跨域肯定是没有问题的,已经使用了这么多天,使用的都是跨域啊。而且使用get没问题,登录(post方式)的时候也没问题。我用fiddler观察了接口,发现这个接口其实返回了json信息 “登录超时,请重新登录”。这更让我百思不得其解,我一度怀疑我的cors跨域是不是真的有问题,还是其他什么莫名的bug。
经过长时间的排查和查找资料后,我发现了问题的所在。
由于我这个项目比较特殊,是前后端分离并且跨域的,所以需要注意的点比较多,突然我找到一篇博客,和我的项目很像,仔细看了下,似乎和我的问题一样,是这位老哥的博客https://blog.****.net/madonghyu/article/details/80027387
他在博客里说:发现当ajax请求为复杂请求时,cookie无法被携带传输到服务器的,导致一直无法访问。
我检查了一下我的请求,用fiddler发现请求时果然没有cookie
原因分析
其实,在post跨域请求时,一个请求会发送2次,第一次是验证请求(options)这个请求时通知后台,我要发送跨域请求了,第二次才是真实的操作请求。由于第一次的options是不带cookie的,所以shiro的身份验证是不通过的,所以才会返回json“登录超时”,第一次验证请求未通过,自然第二次请求报错。只是报错请求头啥的,我没看懂。
解决问题
既然是第一次的options没通过导致的,那我就判断是否是options请求,如果是就直接让它通过。如果不是就走正常的程序
判断是否是options请求的关键代码是
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
setHeader(httpRequest,httpResponse);
return true;
}
在这篇博客里,他是重写shiro的UserFilter,实现通过OPTIONS请求,不是必须重写这个方法,其他方法也行,在xml文件里配置就行了。
我是在重写AuthorizationFilter里使用的,代码如下
package com.wolwo.shiro;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wolwo.base.util.JsonResult;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
public class MyAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
/**
* 在访问过来的时候检测是否为OPTIONS请求,如果是就直接返回true
*/
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
setHeader(httpRequest,httpResponse);
return true;
}
//获得当前对象
Subject subject = getSubject(servletRequest, servletResponse);
//判断是否已登录
if (null != subject.getPrincipals()) {
return true;
}
//可在此处根据session中存放的用户权限,比对路径,如果拥有该权限则放行
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws IOException {
saveRequest(request);
setHeader((HttpServletRequest) request,(HttpServletResponse) response);
PrintWriter out = response.getWriter();
//对象转json,传给前台接收
ObjectMapper mapper = new ObjectMapper();
JsonResult jsonResult = new JsonResult(JsonResult.NOTLOGIN, "登录超时,请重新登录");
String result = mapper.writeValueAsString(jsonResult);
out.println(result);
out.flush();
out.close();
return false;
}
/**
* 为response设置header,实现跨域
*/
private void setHeader(HttpServletRequest request,HttpServletResponse response){
//跨域的header设置
response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", request.getMethod());
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
//防止乱码,适用于传输JSON数据
response.setHeader("Content-Type","application/json;charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
}
}
这样请求就ok了