废话不多说,先上解决办法,后边再说原理:
自定义MyFormAuthenticationFilter
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
/**
* 在访问controller前判断是否登录,返回json,不进行重定向。
* @param request
* @param response
* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (isAjax(request)) {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
//解决一下跨域问题
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Max-Age", "0");
httpServletResponse.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");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("XDomainRequestAllowed","1");
httpServletResponse.getWriter().write(BaseRestfulResult.needLogin().toJsonString());
httpServletResponse.getWriter().flush();
httpServletResponse.getWriter().close();
} else {
//saveRequestAndRedirectToLogin(request, response);
/**
* @Mark 非ajax请求重定向为登录页面
*/
httpServletResponse.sendRedirect("/login");
}
return false;
}
//解决OPTIONS请求跨域问题
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
private boolean isAjax(ServletRequest request){
String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
if("XMLHttpRequest".equalsIgnoreCase(header)){
return Boolean.TRUE;
}
return Boolean.FALSE;
return true;
}
}
注意上边的isAccessAllowed()方法和onAccessDenied()方法中的几行httpServletResponse.setHeader(“xx”, “xxx”);,这两个地方是关键。
最后别忘了在config类中注册一下
shiroFilterFactoryBean.getFilters().put(“authc”, new MyFormAuthenticationFilter());
大功告成~
以下是原理
最近在用springboot写前后端分离项目的时候, 用postman测试接口没问题,但丢给前端测试的时候返回了跨域问题:
这就很奇怪,所有的controller上我都加了@CrossOrigin注解,为什么还会有跨域问题呢?
这里有个需要注意的地方: xx has been blocked by CORS policy
这个CORS是个什么东西呢?简单说是一种资源共享机制。当浏览器发起ajax请求的时候,会先发起一个method为OPTIONS的请求, 这个请求我们可以简单理解为一个探路请求, 该请求不携带信息, 只是为了测试一下目标服务器是否支持跨域,如果支持跨域的话,再发出后续的请求。
通过chrome控制台可以看出, 当我们尝试调用listByAdmin接口的时候,发出了一个OPTIONS请求,该请求返回200后,浏览器又发起了一个一模一样的请求,后边这个请求才是真正的请求,当然这是正常的情况。
问题就出在这个OPTIONS请求上, 因为我的项目是完全前后端分离的,前端请求的时候会在http header上携带token,后端利用token鉴权。然而OPTIONS请求(‘OPTIONS请求’这个叫法可能不准确,各位理解我说的是啥就行)默认是不携带token信息的,后端没有收到token,导致鉴权失败。
但是鉴权失败为什么报鉴权问题,反而报了个跨域问题呢?
先看一下一开始我是怎么处理鉴权失败的
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (isAjax(request)) {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
//返回给前端鉴权失败的信息
httpServletResponse.getWriter().write(BaseRestfulResult.needLogin().toJsonString());
httpServletResponse.getWriter().flush();
httpServletResponse.getWriter().close();
} else {
//saveRequestAndRedirectToLogin(request, response);
/**
* @Mark 非ajax请求重定向为登录页面
*/
httpServletResponse.sendRedirect("/login");
}
return false;
}
}
首先判断是否ajax请求,如果是的话,将鉴权失败的信息返回给前端。 这个地方没有设置允许跨域的header,所以前端就出现了跨域问题。
解决之后的源码:
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (isAjax(request)) {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
//解决一下跨域问题
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Max-Age", "0");
httpServletResponse.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");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("XDomainRequestAllowed","1");
httpServletResponse.getWriter().write(BaseRestfulResult.needLogin().toJsonString());
httpServletResponse.getWriter().flush();
httpServletResponse.getWriter().close();
} else {
//saveRequestAndRedirectToLogin(request, response);
/**
* @Mark 非ajax请求重定向为登录页面
*/
httpServletResponse.sendRedirect("/login");
}
return false;
}
当然,最佳的解决方案是针对OPTIONS请求单独处理以下
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}