非简单请求中POST请求的Options预请求403异常的跨域处理

发布时间 2023-07-13 16:16:06作者: kelelipeng

非简单请求中POST请求的Options预请求403异常的跨域处理

https://codeleading.com/article/9125305704/

 

 

 在http请求中,post请求的数据在请求体中,在spring MVC中通过@RequestBody接收。
 post请求属于http请求中的复杂请求,http协议在浏览器中对复杂请求会先发起一次Options的预请求,发起Options请求常会报403错误:
   Failed to load https://one.xxx.com/abd : Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://other.xxxx.com' is therefore not allowed access. The response had HTTP status code 403.
   针对这种情况,通常是在DispacerServlet中没有找都到执行Options请求的方法。在做跨域处理时,通常配置好跨域请求头信息后,常常忽略在spring MVC中添加对Options请求的处理。
   解决办法有三种:
   1. 在Filter中添加对Options请求的支持处理
   public class TokenFilter implements Filter {

        @Value("${redirect.login.page.url}")
        private String loginPageUrl;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }

        @Override
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            **setCorsFilter(request, response);**   // 设置跨域响应头
            
            String method = ((HttpServletRequest) request).getMethod();
            **if (method.equals("OPTIONS")) {
                response.setStatus(HttpServletResponse.SC_OK);
            } else {**
                String sessionToken = request.getSession(false) == null ? null
                        : (String) request.getSession(false).getAttribute(AppConstants.RISK_ACCESS_TOKEN);
                String token = RequestUtils.getHeaderToken(request, AppConstants.RISK_ACCESS_TOKEN);
            
                // 校验token有效性
                boolean isValid = validToken(sessionToken, token);
                if (!isValid) {
                    log.error("====> token is invalid, redirect to login page...");
                    redirectToLoginPage(request, response);
                } else {
                    log.info("====> TokenFilter处理完成,执行下一个Filter...");
                    chain.doFilter(req, resp);
                }
            }
        }

        /****
         * 设置跨域问题
         * 
         * @param request
         * @param response
         */
        private void setCorsFilter(HttpServletRequest request, HttpServletResponse response) {
            response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "trm_access_token,X-Requested-With,Content-Type");
        }
    
        /****
         * 校验token有效性
         * 
         * @param sessionToken
         * @param token
         * @return
         * @author wangzhicheng
         * @since 2019.01.03
         */
        private boolean validToken(String sessionToken, String token) {
            if (StringUtils.isEmpty(token) || StringUtils.isEmpty(sessionToken)) {
                return false;
            }
            if (TokenUtil.validToken(token)) {
                // String sessionToken = (String)
                // request.getSession(false).getAttribute(AppConstants.RISK_ACCESS_TOKEN);
                // if (StringUtils.isEmpty(sessionToken)) {
                // return false;
                // }
                return sessionToken.equals(token); // 校验是不是当前用户的token
            }
            return false;
        }
    
        /**
         * 跳转登录
         * 
         * @param request
         * @param response
         * @throws IOException
         */
        private void redirectToLoginPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
            if (isAjaxRequest(request)) {
                response.setContentType("application/json;charset=UTF-8");
                response.setStatus(999);
                PrintWriter writer = null;
                OutputStreamWriter osw = null;
                try {
                    osw = new OutputStreamWriter(response.getOutputStream(), "UTF-8");
                    writer = response.getWriter();
                    Response responseMsg = Response.failure(AuthExpTips.USER_IS_NOT_LOGIN_OR_IS_INVALID.getMessage());
                    responseMsg.setCode(AuthExpTips.USER_IS_NOT_LOGIN_OR_IS_INVALID.getCode());
                    String jsonStr = JSON.toJSONString(responseMsg);
    //                writer.write(GsonUtil.toJson(responseMsg));
                    writer.write(jsonStr);
                    writer.flush();
                    writer.close();
                    osw.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    if (null != writer) { 
                        writer.close(); 
                    } 
                    if (null != osw) { 
                        osw.close(); 
                    }
                }
            } else {
                request.getSession().invalidate();
                response.sendRedirect(loginPageUrl);
            }
        }
    
        /**
         * 是否Ajax请求
         * 
         * @param request
         * @return
         */
        private boolean isAjaxRequest(HttpServletRequest request) {
            String requestedWith = request.getHeader("X-Requested-With");
            return requestedWith != null ? "XMLHttpRequest".equals(requestedWith) : false;
        }
    
        @Override
        public void destroy() {
        }
    }

   2. 在拦截器中添加对Options请求的支持处理
     public class CrosInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                                 Object handler) throws Exception {
            if(request.getMethod().equals(RequestMethod.OPTIONS.name())) {
                response.setStatus(HttpStatus.OK.value());
                return false;
            }
            return true;
        }
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response,
                               Object handler, ModelAndView modelAndView) throws Exception {
        }
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                    Object handler, Exception ex) throws Exception {
        }
    }

   3. 添加一个支持Options的ReqeuestMapping
   @RequestMapping(value = "/*",method = RequestMethod.OPTIONS)
    public ResponseEntity handleOptions(){
        return ResponseEntity.noContent();
    }

附:
如果是http请求,nginx配置参考:
添加了三个用于控制CORS的头信息:
1)Access-Control-Allow-Origin:允许的来源
2)Access-Control-Allow-Credentials:设置为true,允许ajax异步请求带cookie信息
3) Access-Control-Allow-Headers:设置为x-requested-with,content-type,允许ajax余部请求。
location / {
add_header ‘Access-Control-Allow-Origin’ ‘https://one.xxxx.com’;
add_header “Access-Control-Allow-Credentials” “true”;
add_header “Access-Control-Allow-Headers” “x-requested-with,content-type”;
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}