1. 程式人生 > >非簡單請求中POST請求的Options預請求403異常的跨域處理

非簡單請求中POST請求的Options預請求403異常的跨域處理

 在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;
}