com.binarybirchtree.filters.IpFilter.java Source code

Java tutorial

Introduction

Here is the source code for com.binarybirchtree.filters.IpFilter.java

Source

// A Java servlet filter that ensures that visitors access your web application
// through Cloudflare (or any other IP addresses of your choice).

// Copyright 2013 Binary Birch Tree
// http://www.binarybirchtree.com

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.binarybirchtree.filters;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.net.util.SubnetUtils;
import org.springframework.web.filter.RequestContextFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

public class IpFilter extends RequestContextFilter {
    // Get the user's real IP address, accounting for Cloudflare and Heroku proxying.
    public static String getForwardedIp(HttpServletRequest request) {
        String ip = null;

        // Believe the Cloudflare header only if the last IP was from Cloudflare.
        if (ipIsInList(getForwardedIp(request, 1), cloudflareIps))
            ip = request.getHeader("CF-Connecting-IP");

        // If there is no Cloudflare header, return the last known reliable IP.
        if (ip == null)
            ip = getForwardedIp(request, 1);
        return ip;
    }

    // Get the n-th latest IP from the X-Forwarded-For header.
    public static String getForwardedIp(HttpServletRequest request, int degree) {
        if (degree == 0) {
            return request.getRemoteAddr();
        }

        // Believe the X-Forwarded-For header only if it came from a trusted internal IP.
        if (!ipIsInList(request.getRemoteAddr(), internalIps)) {
            System.err.println(
                    "Last IP address was " + request.getRemoteAddr() + ", which is not a trusted internal IP.");
            return null;
        }

        // Return null if the X-Forwarded-For header was not found.
        String header = request.getHeader("X-Forwarded-For");
        if (header == null)
            return null;

        String[] ips = header.split(",");
        if (degree > ips.length)
            return null;
        return ips[ips.length - degree].trim();
    }

    // Check if the IP falls within any of the CIDR IP ranges in the list.
    public static boolean ipIsInList(String ip, String[] list) {
        if (ip == null)
            return false;

        int failed = 0;
        for (String listIp : list) {
            SubnetUtils subnet = new SubnetUtils(listIp);
            subnet.setInclusiveHostCount(true);

            if (!subnet.getInfo().isInRange(ip))
                failed++;
        }

        return failed < list.length;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        // Do not block localhost.
        if (request.getRemoteAddr().equals("127.0.0.1")) {
            filterChain.doFilter(request, response);
            return;
        }

        boolean allowed = true;

        // Refuse connections that circumvented Cloudflare.
        // Checking the latest IP from the X-Forwarded-For header on Heroku, since request.getRemoteAddr() seems to return an IP in Heroku's internal network.
        if (!ipIsInList(request.getRemoteAddr(), internalIps) || (getForwardedIp(request, 1) != null
                && !ipIsInList(getForwardedIp(request, 1), bypassCloudflareIps)
                && !ipIsInList(getForwardedIp(request, 1), cloudflareIps)))
            allowed = false;

        if (allowed) {
            // Check if the IP before Cloudflare is blacklisted.
            String proxiedIp = getForwardedIp(request, 2);

            if (proxiedIp != null) {
                for (String ip : blacklistIps) {
                    SubnetUtils subnet = new SubnetUtils(ip);
                    subnet.setInclusiveHostCount(true);
                    if (!subnet.getInfo().isInRange(proxiedIp)) {
                        allowed = false;
                        break;
                    }
                }
            }
        }

        // If the request failed one of the tests, send an error response and do not continue processing the request.
        if (!allowed) {
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
            return;
        }

        // If the request passed the tests, allow it to be processed normally.
        filterChain.doFilter(request, response);
    }

    public static final String[] cloudflareIps = { "204.93.240.0/24", "204.93.177.0/24", "199.27.128.0/21",
            "173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22", "103.31.4.0/22", "141.101.64.0/18",
            "108.162.192.0/18", "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22", "198.41.128.0/17",
            "162.158.0.0/15", };

    public static final String[] bypassCloudflareIps = {
            // UptimeRobot IPs
            "74.86.158.106/32", "74.86.158.107/32", "74.86.179.130/32", "74.86.179.131/32", "46.137.190.132/32",
            "122.248.234.23/32", };

    public static final String[] internalIps = {
            // Heroku internal IPs
            "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.1/32", };

    public static final String[] blacklistIps = {};
}