非簡單請求中POST請求的Options預請求403異常的跨域處理
阿新 • • 發佈:2019-01-05
在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;
}