tomcat應用CORS跨域支援
阿新 • • 發佈:2018-12-02
本次使用CORS支援跨域,支援cookie傳遞(使用者登入認證)
後臺過濾器程式碼:(借鑑:D:\apache-tomcat-7.0.54\lib\catalina.jar!\org\apache\catalina\filters\CorsFilter.class)
jar包下載:https://pan.baidu.com/s/1n6Ol7Hq1Tfzh-bUcv6nxAw
package MyCorsFilter; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.res.StringManager; /** * 處理跨域請求,支援cookie * 來源:org.apache.catalina.filters.CorsFilter tomcat下lib catalina.jar * lib下的jar包是拷貝的tomcat下的lib目錄中的 * @author hqj */ public final class MyCorsFilter implements Filter { private static final Log log = LogFactory.getLog(MyCorsFilter.class); private static final StringManager sm = StringManager.getManager("org.apache.catalina.filters"); private final Collection<String> allowedOrigins; private boolean anyOriginAllowed; private final Collection<String> allowedHttpMethods; private final Collection<String> allowedHttpHeaders; private final Collection<String> exposedHeaders; private boolean supportsCredentials; private long preflightMaxAge; private boolean decorateRequest; public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; public static final String RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; public static final String RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; public static final String REQUEST_HEADER_ORIGIN = "Origin"; public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; public static final String HTTP_REQUEST_ATTRIBUTE_PREFIX = "cors."; public static final String HTTP_REQUEST_ATTRIBUTE_ORIGIN = "cors.request.origin"; public static final String HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST = "cors.isCorsRequest"; public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE = "cors.request.type"; public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS = "cors.request.headers"; public MyCorsFilter() { this.allowedOrigins = new HashSet(); this.allowedHttpMethods = new HashSet(); this.allowedHttpHeaders = new HashSet(); this.exposedHeaders = new HashSet(); } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if ((!(servletRequest instanceof HttpServletRequest)) || (!(servletResponse instanceof HttpServletResponse))) { throw new ServletException(sm.getString("corsFilter.onlyHttp")); } HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; CORSRequestType requestType = checkRequestType(request); if (this.decorateRequest) { decorateCORSProperties(request, requestType); } switch (requestType) { case SIMPLE: handleSimpleCORS(request, response, filterChain); break; case ACTUAL: handleSimpleCORS(request, response, filterChain); break; case PRE_FLIGHT: handlePreflightCORS(request, response, filterChain); break; case NOT_CORS: handleNonCORS(request, response, filterChain); break; default: handleInvalidCORS(request, response, filterChain); } } public void init(FilterConfig filterConfig) throws ServletException { parseAndStore("*", "GET,POST,HEAD,OPTIONS", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers", "", "true", "1800", "true"); if (filterConfig != null) { String configAllowedOrigins = filterConfig.getInitParameter("cors.allowed.origins"); String configAllowedHttpMethods = filterConfig.getInitParameter("cors.allowed.methods"); String configAllowedHttpHeaders = filterConfig.getInitParameter("cors.allowed.headers"); String configExposedHeaders = filterConfig.getInitParameter("cors.exposed.headers"); String configSupportsCredentials = filterConfig.getInitParameter("cors.support.credentials"); String configPreflightMaxAge = filterConfig.getInitParameter("cors.preflight.maxage"); String configDecorateRequest = filterConfig.getInitParameter("cors.request.decorate"); parseAndStore(configAllowedOrigins, configAllowedHttpMethods, configAllowedHttpHeaders, configExposedHeaders, configSupportsCredentials, configPreflightMaxAge, configDecorateRequest); } } protected void handleSimpleCORS(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { CORSRequestType requestType = checkRequestType(request); if ((requestType != CORSRequestType.SIMPLE) && (requestType != CORSRequestType.ACTUAL)) { throw new IllegalArgumentException(sm.getString("corsFilter.wrongType2", new Object[] { CORSRequestType.SIMPLE, CORSRequestType.ACTUAL })); } String origin = request.getHeader("Origin"); String method = request.getMethod(); if (!isOriginAllowed(origin)) { handleInvalidCORS(request, response, filterChain); return; } if (!this.allowedHttpMethods.contains(method)) { handleInvalidCORS(request, response, filterChain); return; } if ((this.anyOriginAllowed) && (!this.supportsCredentials)) { response.addHeader("Access-Control-Allow-Origin", "*"); } else { response.addHeader("Access-Control-Allow-Origin", origin); } if (this.supportsCredentials) { response.addHeader("Access-Control-Allow-Credentials", "true"); } if ((this.exposedHeaders != null) && (this.exposedHeaders.size() > 0)) { String exposedHeadersString = join(this.exposedHeaders, ","); response.addHeader("Access-Control-Expose-Headers", exposedHeadersString); } filterChain.doFilter(request, response); } protected void handlePreflightCORS(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { CORSRequestType requestType = checkRequestType(request); if (requestType != CORSRequestType.PRE_FLIGHT) { throw new IllegalArgumentException(sm.getString("corsFilter.wrongType1", new Object[] { CORSRequestType.PRE_FLIGHT.name().toLowerCase() })); } String origin = request.getHeader("Origin"); if (!isOriginAllowed(origin)) { handleInvalidCORS(request, response, filterChain); return; } String accessControlRequestMethod = request.getHeader("Access-Control-Request-Method"); if ((accessControlRequestMethod == null) || (!HTTP_METHODS.contains(accessControlRequestMethod.trim()))) { handleInvalidCORS(request, response, filterChain); return; } accessControlRequestMethod = accessControlRequestMethod.trim(); String accessControlRequestHeadersHeader = request.getHeader("Access-Control-Request-Headers"); List<String> accessControlRequestHeaders = new LinkedList(); if ((accessControlRequestHeadersHeader != null) && (!accessControlRequestHeadersHeader.trim().isEmpty())) { String[] headers = accessControlRequestHeadersHeader.trim().split(","); for (String header : headers) { accessControlRequestHeaders.add(header.trim().toLowerCase()); } } if (!this.allowedHttpMethods.contains(accessControlRequestMethod)) { handleInvalidCORS(request, response, filterChain); return; } if (!accessControlRequestHeaders.isEmpty()) { for (String header : accessControlRequestHeaders) { if (!this.allowedHttpHeaders.contains(header)) { handleInvalidCORS(request, response, filterChain); return; } } } if (this.supportsCredentials) { response.addHeader("Access-Control-Allow-Origin", origin); response.addHeader("Access-Control-Allow-Credentials", "true"); } else if (this.anyOriginAllowed) { response.addHeader("Access-Control-Allow-Origin", "*"); } else { response.addHeader("Access-Control-Allow-Origin", origin); } if (this.preflightMaxAge > 0L) { response.addHeader("Access-Control-Max-Age", String.valueOf(this.preflightMaxAge)); } response.addHeader("Access-Control-Allow-Methods", accessControlRequestMethod); if ((this.allowedHttpHeaders != null) && (!this.allowedHttpHeaders.isEmpty())) { response.addHeader("Access-Control-Allow-Headers", join(this.allowedHttpHeaders, ",")); } } private void handleNonCORS(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(request, response); } private void handleInvalidCORS(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { String origin = request.getHeader("Origin"); String method = request.getMethod(); String accessControlRequestHeaders = request.getHeader("Access-Control-Request-Headers"); response.setContentType("text/plain"); response.setStatus(403); response.resetBuffer(); if (log.isDebugEnabled()) { StringBuilder message = new StringBuilder("Invalid CORS request; Origin="); message.append(origin); message.append(";Method="); message.append(method); if (accessControlRequestHeaders != null) { message.append(";Access-Control-Request-Headers="); message.append(accessControlRequestHeaders); } log.debug(message.toString()); } } public void destroy() { } protected static void decorateCORSProperties(HttpServletRequest request, CORSRequestType corsRequestType) { if (request == null) { throw new IllegalArgumentException(sm.getString("corsFilter.nullRequest")); } if (corsRequestType == null) { throw new IllegalArgumentException(sm.getString("corsFilter.nullRequestType")); } switch (corsRequestType) { case SIMPLE: request.setAttribute("cors.isCorsRequest", Boolean.TRUE); request.setAttribute("cors.request.origin", request.getHeader("Origin")); request.setAttribute("cors.request.type", corsRequestType.name().toLowerCase()); break; case ACTUAL: request.setAttribute("cors.isCorsRequest", Boolean.TRUE); request.setAttribute("cors.request.origin", request.getHeader("Origin")); request.setAttribute("cors.request.type", corsRequestType.name().toLowerCase()); break; case PRE_FLIGHT: request.setAttribute("cors.isCorsRequest", Boolean.TRUE); request.setAttribute("cors.request.origin", request.getHeader("Origin")); request.setAttribute("cors.request.type", corsRequestType.name().toLowerCase()); String headers = request.getHeader("Access-Control-Request-Headers"); if (headers == null) { headers = ""; } request.setAttribute("cors.request.headers", headers); break; case NOT_CORS: request.setAttribute("cors.isCorsRequest", Boolean.FALSE); break; } } protected static String join(Collection<String> elements, String joinSeparator) { String separator = ","; if (elements == null) { return null; } if (joinSeparator != null) { separator = joinSeparator; } StringBuilder buffer = new StringBuilder(); boolean isFirst = true; for (String element : elements) { if (!isFirst) { buffer.append(separator); } else { isFirst = false; } if (element != null) { buffer.append(element); } } return buffer.toString(); } protected CORSRequestType checkRequestType(HttpServletRequest request) { CORSRequestType requestType = CORSRequestType.INVALID_CORS; if (request == null) { throw new IllegalArgumentException(sm.getString("corsFilter.nullRequest")); } String originHeader = request.getHeader("Origin"); if (originHeader != null) { if (originHeader.isEmpty()) { requestType = CORSRequestType.INVALID_CORS; } else if (!isValidOrigin(originHeader)) { requestType = CORSRequestType.INVALID_CORS; } else { String method = request.getMethod(); if ((method != null) && (HTTP_METHODS.contains(method))) { if ("OPTIONS".equals(method)) { String accessControlRequestMethodHeader = request.getHeader("Access-Control-Request-Method"); if ((accessControlRequestMethodHeader != null) && (!accessControlRequestMethodHeader.isEmpty())) { requestType = CORSRequestType.PRE_FLIGHT; } else if ((accessControlRequestMethodHeader != null) && (accessControlRequestMethodHeader.isEmpty())) { requestType = CORSRequestType.INVALID_CORS; } else { requestType = CORSRequestType.ACTUAL; } } else if (("GET".equals(method)) || ("HEAD".equals(method))) { requestType = CORSRequestType.SIMPLE; } else if ("POST".equals(method)) { String contentType = request.getContentType(); if (contentType != null) { contentType = contentType.toLowerCase().trim(); if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES.contains(contentType)) { requestType = CORSRequestType.SIMPLE; } else { requestType = CORSRequestType.ACTUAL; } } // 如果contentType欄位為可空就預設為是簡單跨域,防止這個欄位的缺失匯出跨域失敗 hqj 2018.10.19 else { requestType = CORSRequestType.SIMPLE; } } else if (COMPLEX_HTTP_METHODS.contains(method)) { requestType = CORSRequestType.ACTUAL; } } } } else { requestType = CORSRequestType.NOT_CORS; } return requestType; } private boolean isOriginAllowed(String origin) { if (this.anyOriginAllowed) { return true; } return this.allowedOrigins.contains(origin); } private void parseAndStore(String allowedOrigins, String allowedHttpMethods, String allowedHttpHeaders, String exposedHeaders, String supportsCredentials, String preflightMaxAge, String decorateRequest) throws ServletException { if (allowedOrigins != null) { if (allowedOrigins.trim().equals("*")) { this.anyOriginAllowed = true; } else { this.anyOriginAllowed = false; Set<String> setAllowedOrigins = parseStringToSet(allowedOrigins); this.allowedOrigins.clear(); this.allowedOrigins.addAll(setAllowedOrigins); } } if (allowedHttpMethods != null) { Set<String> setAllowedHttpMethods = parseStringToSet(allowedHttpMethods); this.allowedHttpMethods.clear(); this.allowedHttpMethods.addAll(setAllowedHttpMethods); } if (allowedHttpHeaders != null) { Set<String> setAllowedHttpHeaders = parseStringToSet(allowedHttpHeaders); Set<String> lowerCaseHeaders = new HashSet(); for (String header : setAllowedHttpHeaders) { String lowerCase = header.toLowerCase(); lowerCaseHeaders.add(lowerCase); } this.allowedHttpHeaders.clear(); this.allowedHttpHeaders.addAll(lowerCaseHeaders); } if (exposedHeaders != null) { Set<String> setExposedHeaders = parseStringToSet(exposedHeaders); this.exposedHeaders.clear(); this.exposedHeaders.addAll(setExposedHeaders); } if (supportsCredentials != null) { this.supportsCredentials = Boolean.parseBoolean(supportsCredentials); } if (preflightMaxAge != null) { try { if (!preflightMaxAge.isEmpty()) { this.preflightMaxAge = Long.parseLong(preflightMaxAge); } else { this.preflightMaxAge = 0L; } } catch (NumberFormatException e) { throw new ServletException(sm.getString("corsFilter.invalidPreflightMaxAge"), e); } } if (decorateRequest != null) { this.decorateRequest = Boolean.parseBoolean(decorateRequest); } } private Set<String> parseStringToSet(String data) { String[] splits; if ((data != null) && (data.length() > 0)) { splits = data.split(","); } else { splits = new String[0]; } Set<String> set = new HashSet(); if (splits.length > 0) { for (String split : splits) { set.add(split.trim()); } } return set; } protected static boolean isValidOrigin(String origin) { // 去掉協議名稱驗證,防止 "192.168.2.225:8080"不通過 hqj 2018-10-19 return true; /* * if (origin.contains("%")) { return false; } URI originURI; try { originURI = * new URI(origin); } catch (URISyntaxException e) { return false; } return * originURI.getScheme() != null; */ } public boolean isAnyOriginAllowed() { return this.anyOriginAllowed; } public Collection<String> getExposedHeaders() { return this.exposedHeaders; } public boolean isSupportsCredentials() { return this.supportsCredentials; } public long getPreflightMaxAge() { return this.preflightMaxAge; } public Collection<String> getAllowedOrigins() { return this.allowedOrigins; } public Collection<String> getAllowedHttpMethods() { return this.allowedHttpMethods; } public Collection<String> getAllowedHttpHeaders() { return this.allowedHttpHeaders; } protected static enum CORSRequestType { SIMPLE, ACTUAL, PRE_FLIGHT, NOT_CORS, INVALID_CORS; private CORSRequestType() { } } public static final Collection<String> HTTP_METHODS = new HashSet( Arrays.asList(new String[] { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT" })); public static final Collection<String> COMPLEX_HTTP_METHODS = new HashSet( Arrays.asList(new String[] { "PUT", "DELETE", "TRACE", "CONNECT" })); public static final Collection<String> SIMPLE_HTTP_METHODS = new HashSet( Arrays.asList(new String[] { "GET", "POST", "HEAD" })); public static final Collection<String> SIMPLE_HTTP_REQUEST_HEADERS = new HashSet( Arrays.asList(new String[] { "Accept", "Accept-Language", "Content-Language" })); public static final Collection<String> SIMPLE_HTTP_RESPONSE_HEADERS = new HashSet(Arrays.asList(new String[] { "Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma" })); public static final Collection<String> SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES = new HashSet( Arrays.asList(new String[] { "application/x-www-form-urlencoded", "multipart/form-data", "text/plain" })); public static final String DEFAULT_ALLOWED_ORIGINS = "*"; public static final String DEFAULT_ALLOWED_HTTP_METHODS = "GET,POST,HEAD,OPTIONS"; public static final String DEFAULT_PREFLIGHT_MAXAGE = "1800"; public static final String DEFAULT_SUPPORTS_CREDENTIALS = "true"; public static final String DEFAULT_ALLOWED_HTTP_HEADERS = "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers"; public static final String DEFAULT_EXPOSED_HEADERS = ""; public static final String DEFAULT_DECORATE_REQUEST = "true"; public static final String PARAM_CORS_ALLOWED_ORIGINS = "cors.allowed.origins"; public static final String PARAM_CORS_SUPPORT_CREDENTIALS = "cors.support.credentials"; public static final String PARAM_CORS_EXPOSED_HEADERS = "cors.exposed.headers"; public static final String PARAM_CORS_ALLOWED_HEADERS = "cors.allowed.headers"; public static final String PARAM_CORS_ALLOWED_METHODS = "cors.allowed.methods"; public static final String PARAM_CORS_PREFLIGHT_MAXAGE = "cors.preflight.maxage"; public static final String PARAM_CORS_REQUEST_DECORATE = "cors.request.decorate"; }
應用web.xml配置這個過濾器(如果想實現tomcat下所有應用跨域,可配置到 D:\apache-tomcat-7.0.54\conf\web.xml):
<!--跨域過濾器,支援cookie傳遞--> <filter> <filter-name>CorsFilter</filter-name> <filter-class>MyCorsFilter.MyCorsFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.allowed.methods</param-name> <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value> </init-param> <init-param> <param-name>cors.allowed.headers</param-name> <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> </init-param> <init-param> <param-name>cors.exposed.headers</param-name> <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value> </init-param> <init-param> <param-name>cors.support.credentials</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>cors.preflight.maxage</param-name> <param-value>100</param-value> </init-param> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
前端指令碼程式碼:
$.ajax({ type: 'post', url: "http://192.168.3.125:8080/test/query", contentType: "application/json", data: JSON.stringify({ "paras": { "name": "xiaoming" } } }), xhrFields: { withCredentials: true }, crossDomain: true, success: function (res) { console.log("成功了", arguments); }, error: function () { console.error("失敗了", arguments); } })