Skip to content

Instantly share code, notes, and snippets.

@hengyunabc
Created April 17, 2015 08:46
Show Gist options
  • Save hengyunabc/f812cb7098f1222ec0dd to your computer and use it in GitHub Desktop.
Save hengyunabc/f812cb7098f1222ec0dd to your computer and use it in GitHub Desktop.
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