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.csdn.net/madonghyu/article/details/80027387

他在博客里说:发现当ajax请求为复杂请求时,cookie无法被携带传输到服务器的,导致一直无法访问。 

我检查了一下我的请求,用fiddler发现请求时果然没有cookie

ssm+vue 使用shiro后 post请求报错 Request header field Content-Type is not allowed by Access-Control-Allow-H

原因分析

其实,在post跨域请求时,一个请求会发送2次,第一次是验证请求(options)这个请求时通知后台,我要发送跨域请求了,第二次才是真实的操作请求。由于第一次的options是不带cookie的,所以shiro的身份验证是不通过的,所以才会返回json“登录超时”,第一次验证请求未通过,自然第二次请求报错。只是报错请求头啥的,我没看懂。

ssm+vue 使用shiro后 post请求报错 Request header field Content-Type is not allowed by Access-Control-Allow-H

解决问题

既然是第一次的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());

    }

}

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-H

这样请求就ok了