Created
April 17, 2015 08:46
-
-
Save hengyunabc/f812cb7098f1222ec0dd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.IOException; | |
import java.net.Inet4Address; | |
import java.net.Inet6Address; | |
import java.net.InetAddress; | |
import java.net.NetworkInterface; | |
import java.net.SocketException; | |
import java.net.URL; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Enumeration; | |
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.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
/** | |
* <pre> | |
* 支持的配置项: | |
* matchMethods 即拦截的方法,默认值"POST|PUT|DELETE|CONNECT|PATCH",通常不用配置 | |
* allowSubDomainHosts 匹配子域名,以"|"分隔,如"test.com|abc.com", | |
* 则http://test.com, http://xxx.test.com这样的请求都会匹配到,推荐优先使用这个配置 | |
* completeMatchHosts 完全匹配的域名,以"|"分隔,如"test.com|abc.com",则只有http://test.com 这样的请求会匹配 | |
* 像http://www.test.com 这样的请求不会被匹配 | |
* | |
* responseError 被拦截的请求的response的返回值,默认是403 | |
* redirectPath 被拦截的请求重定向到的url,如果配置了这个值,则会忽略responseError的配置。 | |
* 比如可以配置重定向到自己定义的错误页: /referer_error.html | |
* bAllowEmptyReferer 是否允许空referer,默认是false,除非很清楚,否则不要改动这个 | |
* bAllowLocalhost 是否允许localhost, 127.0.0.1 这样的referer的请求,默认是true,便于调试 | |
* bAllowAllIPAndHost 是否允许本机的所有IP和host的referer请求,默认是false | |
* | |
* {@code | |
* <filter> | |
* <filter-name>refererFilter</filter-name> | |
* <filter-class>com.test.RefererFilter</filter-class> | |
* <init-param> | |
* <param-name>completeMatchHosts</param-name> | |
* <param-value>test.com|abc.com</param-value> | |
* </init-param> | |
* <init-param> | |
* <param-name>allowSubDomainHosts</param-name> | |
* <param-value>hello.com|xxx.yyy.com</param-value> | |
* </init-param> | |
* </filter> | |
* | |
* <filter-mapping> | |
* <filter-name>refererFilter</filter-name> | |
* <url-pattern>/*</url-pattern> | |
* </filter-mapping> | |
* } | |
* </pre> | |
* | |
* @author hengyunabc | |
* | |
*/ | |
public class RefererFilter implements Filter { | |
static final Logger logger = LoggerFactory.getLogger(RefererFilter.class); | |
public static final String DEFAULT_MATHMETHODS = "POST|PUT|DELETE|CONNECT|PATCH"; | |
List<String> mathMethods = new ArrayList<>(); | |
boolean bAllowEmptyReferer = false; | |
boolean bAllowLocalhost = true; | |
boolean bAllowAllIPAndHost = false; | |
/** | |
* when bAllowSubDomain is true, allowHosts is "test.com", then | |
* "www.test.com", "xxx.test.com" will be allow. | |
*/ | |
boolean bAllowSubDomain = false; | |
String redirectPath = null; | |
int responseError = HttpServletResponse.SC_FORBIDDEN; | |
HashSet<String> completeMatchHosts = new HashSet<String>(); | |
List<String> allowSubDomainHostList = new ArrayList<String>(); | |
@Override | |
public void init(FilterConfig filterConfig) throws ServletException { | |
mathMethods.addAll(getSplitStringList(filterConfig, "matchMethods", "\\|", DEFAULT_MATHMETHODS)); | |
completeMatchHosts.addAll(getSplitStringList(filterConfig, "completeMatchHosts", "\\|", "")); | |
List<String> allowSubDomainHosts = getSplitStringList(filterConfig, "allowSubDomainHosts", "\\|", ""); | |
completeMatchHosts.addAll(allowSubDomainHosts); | |
for (String host : allowSubDomainHosts) { | |
// check the first char if is '.' | |
if (!host.isEmpty() && host.charAt(0) != '.') { | |
allowSubDomainHostList.add("." + host); | |
} else { | |
allowSubDomainHostList.add(host); | |
} | |
} | |
responseError = getInt(filterConfig, "responseError", responseError); | |
redirectPath = filterConfig.getInitParameter("redirectPath"); | |
bAllowEmptyReferer = getBoolean(filterConfig, "bAllowEmptyReferer", bAllowEmptyReferer); | |
bAllowLocalhost = getBoolean(filterConfig, "bAllowLocalhost", bAllowLocalhost); | |
if (bAllowLocalhost) { | |
completeMatchHosts.add("localhost"); | |
completeMatchHosts.add("127.0.0.1"); | |
completeMatchHosts.add("[::1]"); | |
} | |
bAllowAllIPAndHost = getBoolean(filterConfig, "bAllowAllIPAndHost", bAllowAllIPAndHost); | |
if (bAllowAllIPAndHost) { | |
completeMatchHosts.addAll(getAllIPAndHost()); | |
} | |
} | |
@Override | |
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, | |
ServletException { | |
if (servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse) { | |
HttpServletRequest request = (HttpServletRequest) servletRequest; | |
HttpServletResponse response = (HttpServletResponse) servletResponse; | |
String method = request.getMethod(); | |
/** | |
* if method not in POST|PUT|DELETE|CONNECT|PATCH, don't check | |
* referrer. | |
*/ | |
if (!mathMethods.contains(method.trim().toUpperCase())) { | |
filterChain.doFilter(request, response); | |
return; | |
} | |
String referrer = request.getHeader("referer"); | |
boolean bAllow = false; | |
if (isBlank(referrer)) { | |
bAllow = bAllowEmptyReferer; | |
} else { | |
URL url = null; | |
try { | |
url = new URL(referrer); | |
String host = url.getHost(); | |
if (completeMatchHosts.contains(host)) { | |
bAllow = true; | |
} else { | |
for (String domain : allowSubDomainHostList) { | |
if (host.endsWith(domain)) { | |
bAllow = true; | |
break; | |
} | |
} | |
} | |
} catch (RuntimeException e) { | |
logger.error("illegal referrer! referrer: " + referrer, e); | |
bAllow = false; | |
} | |
} | |
if (bAllow) { | |
filterChain.doFilter(request, response); | |
return; | |
} else { | |
if (isBlank(redirectPath)) { | |
response.sendError(HttpServletResponse.SC_FORBIDDEN); | |
} else { | |
response.sendRedirect(request.getContextPath() + redirectPath); | |
} | |
} | |
} else { | |
filterChain.doFilter(servletRequest, servletResponse); | |
} | |
} | |
@Override | |
public void destroy() { | |
} | |
private static boolean isBlank(CharSequence cs) { | |
int strLen; | |
if (cs == null || (strLen = cs.length()) == 0) { | |
return true; | |
} | |
for (int i = 0; i < strLen; i++) { | |
if (Character.isWhitespace(cs.charAt(i)) == false) { | |
return false; | |
} | |
} | |
return true; | |
} | |
private static boolean getBoolean(FilterConfig filterConfig, String parameter, boolean defaultParameterValue) { | |
String parameterString = filterConfig.getInitParameter(parameter); | |
if (parameterString == null) { | |
return defaultParameterValue; | |
} | |
return Boolean.parseBoolean(parameterString.trim()); | |
} | |
private static int getInt(FilterConfig filterConfig, String parameter, int defaultParameterValue) { | |
String parameterString = filterConfig.getInitParameter(parameter); | |
if (parameterString == null) { | |
return defaultParameterValue; | |
} | |
return Integer.parseInt(parameterString.trim()); | |
} | |
/** | |
* <pre> | |
* getSplitStringList(filterConfig, "hosts", "\\|", "test.com|abc.com"); | |
* | |
* if hosts is "hello.com|google.com", will return {"hello.com", google.com"}. | |
* if hosts is null, will return {"test.com", "abc.com"} | |
* </pre> | |
* | |
* @param filterConfig | |
* @param parameter | |
* @param regex | |
* @param defaultParameterValue | |
* @return | |
*/ | |
private static List<String> getSplitStringList(FilterConfig filterConfig, String parameter, String regex, String defaultParameterValue) { | |
String parameterString = filterConfig.getInitParameter(parameter); | |
if (parameterString == null) { | |
parameterString = defaultParameterValue; | |
} | |
String[] split = parameterString.split("\\|"); | |
if (split != null) { | |
List<String> resultList = new LinkedList<String>(); | |
for (String method : split) { | |
resultList.add(method.trim()); | |
} | |
return resultList; | |
} | |
return Collections.emptyList(); | |
} | |
public static Set<String> getAllIPAndHost() { | |
HashSet<String> resultSet = new HashSet<String>(); | |
Enumeration<NetworkInterface> interfaces; | |
try { | |
interfaces = NetworkInterface.getNetworkInterfaces(); | |
while (interfaces.hasMoreElements()) { | |
NetworkInterface nic = interfaces.nextElement(); | |
Enumeration<InetAddress> addresses = nic.getInetAddresses(); | |
while (addresses.hasMoreElements()) { | |
InetAddress address = addresses.nextElement(); | |
if (address instanceof Inet4Address) { | |
resultSet.add(address.getHostAddress()); | |
resultSet.add(address.getHostName()); | |
} else if (address instanceof Inet6Address) { | |
// TODO how to process Inet6Address? | |
// resultSet.add("[" + address.getHostAddress() + "]"); | |
// resultSet.add(address.getHostName()); | |
} | |
} | |
} | |
} catch (SocketException e) { | |
logger.error("getAllIPAndHost error!", e); | |
} | |
return resultSet; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment