基于java过滤器实现web系统的IP访问控制

发布时间:2022-11-27 12:00

一.使用场景

  一般情况下,我们设计web系统都会分别设计前台和后台,前台供普通用户访问,给普通用户提供服务.然后后台给系统管理员使用,用于管理维护前台的数据,以及对一些环境的参数配置.对于后台管理一般都是只给公司内部的员工进行访问,所以我们一般要通过IP来限制访问,实现指定的人群才能够访问后台.

二.实现原理

  1. 把允许访问的IP地址,配置到properties文件里.
  2. 编写过滤器,在过滤器的init方法里读取保存IP白名单的properties文件,把配置的IP地址解析出来,存放到一个List集合中.
  3. 在过滤器的doFilter()方法内,获取访问用户的IP地址,然后将用户IP与List集合中的白名单IP列表逐个匹对,一旦有匹配就放行请求;如果都不匹配,则跳转到拒绝访问页面提示用户.

三.代码实现

  1. IP白名单的配置

一般我们要提供三种配置IP白名单的方式

 1). 单个IP地址的配置,多个之间用逗号或分好隔开

 2). IP地址区间方式的配置,多个区间用逗号或分好隔开,如192.168.1.0-192.168.1.10;192.168.1.20-192.168.1.50

 3). 通配符,多个用逗号或分好隔开,如192.168.0.*

示例如下:

#单个IP地址的配置,多个之间用逗号或分好隔开
allowIP=192.168.0.105;192.168.0.108;127.0.0.1

#IP地址区间方式的配置,多个区间用逗号或分好隔开
allowIPRange=192.168.0.10-192.168.0.20;192.168.0.100-192.168.0.110

#通配符,多个用逗号或分好隔开
allowIPWildcard=192.168.0.*;
  1. 过滤器的编写
package com.legendshop.filter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
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 com.legendshop.exception.IPAccessException;
import com.legendshop.exception.IPFormatException;

public class IPFilter implements Filter {
    //用来存放初始化后的IP白名单列表对应的正则表达式
    private List allowRegexList = new ArrayList();

    @Override
    public void init(FilterConfig config) throws ServletException {
        try {
            //在过滤器初始化的时候初始化白名单列表
            initAllowList();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void initAllowList() throws IOException{
        //读取配置文件,并加载到Properties集合中
        InputStream inStream = new FileInputStream("ipFilter.properties");
        Properties prop = new Properties();
        prop.load(inStream);

        //分别获取三种配置方式配置的IP
        String allowIP = prop.getProperty("allowIP");
        String allowIPRange = prop.getProperty("allowIPRange");
        String allowIPWildcard = prop.getProperty("allowIPWildcard");

        //对用户配置的三种方式的IP白名单进行格式校验
        if(!validate(allowIP, allowIPRange, allowIPWildcard)){
            throw new RuntimeException("IP白名单格式定义异常!");
        }

        //将第一种方式配置的ip地址解析出来,添加到存放IP白名单集合
        if(null != allowIP && !allowIP.trim().equals("")){
            String[] address = allowIP.split(",|;");

            if(null != address && address.length > 0){
                for(String ip : address){
                    allowRegexList.add(ip);
                }
            }
        }

        //将第二种方式配置的ip地址解析出来,添加到存放IP白名单集合
        if(null != allowIPRange && !allowIPRange.trim().equals("")){
            String[] addressRanges = allowIPRange.split(",|;");

            if(null != addressRanges && addressRanges.length > 0){
                for(String addrRange : addressRanges){
                    String[] addrParts = addrRange.split("-");

                    if(null != addrParts && addrParts.length >0 && addrParts.length <= 2){
                        String from = addrParts[0];
                        String to = addrParts[1];
                        String prefix = from.substring(0, from.lastIndexOf(".")+1);

                        int start = Integer.parseInt(from.substring(from.lastIndexOf(".")+1,from.length()));
                        int end = Integer.parseInt(to.substring(to.lastIndexOf(".")+1,to.length()));

                        for(int i = start;i <= end;i++){
                            allowRegexList.add(prefix+i);
                        }

                    }else{
                        throw new RuntimeException("IP列表格式定义异常!");
                    }
                }
            }
        }

        //将第三种方式配置的ip地址解析为一条一条的正则表达式,添加到存放IP白名单集合,如对此处不明白可以先看后面的备注
        if(null != allowIPWildcard && !allowIPWildcard.trim().equals("")){
            String[] address = allowIPWildcard.split(",|;");

            if(null != address && address.length > 0){
                for(String addr : address){
                    if(addr.indexOf("*") != -1){
                        //将*,替换成匹配单端ip地址的正则表达式
                        addr = addr.replaceAll("\\*", "(1\\\\d{1,2}|2[0-4]\\\\d|25[0-5]|\\\\d{1,2})");
                        addr = addr.replaceAll("\\.", "\\\\.");//对.进行转义 
                        allowRegexList.add(addr);
                    }else{
                        throw new RuntimeException("IP白名单格式定义异常!");
                    }
                }
            }
        }
    }

    public boolean validate(String allowIP,String allowIPRange,String allowIPWildcard){

        //匹配IP地址每一段的正则
        String regx = "(1\\d{1,2}|2[0-4]\\d|25[0-5]|\\d{1,2})";
        //把四段用点连接起来,那就是匹配整个ip地址的表达式
        String ipRegx = regx + "\\." + regx + "\\."+ regx + "\\." + regx;

        //校验第一种配置方式配置的IP白名单格式是否正确
        Pattern pattern = Pattern.compile("("+ipRegx+")|("+ipRegx+"(,|;))*");
        if(!this.validate(allowIP, pattern)){
            return false;
        }

        //校验第二种配置方式配置的的IP白名单格式是否正确
        pattern = Pattern.compile("("+ipRegx+")\\-("+ipRegx+")|"+ "(("+ipRegx+")\\-("+ipRegx+")(,|;))*");
        if(!this.validate(allowIPRange, pattern)){
            return false;
        }

        //校验第三种配置方式配置的的IP白名单格式是否正确
        pattern = Pattern.compile("("+regx+"\\."+ regx+"\\."+regx+"\\."+ "\\*)|"+"("+regx+"\\."+regx+"\\."+regx+"\\."+ "\\*(,|;))*");
        if(!this.validate(allowIPWildcard, pattern)){
            return false;
        }
        return true;
    }

    //校验用户配置的ip列表格式是否正确
    public boolean validate(String allowIP,Pattern pattern){
        //如果为空则不做处理
        if(null != allowIP && !allowIP.trim().equals("")){
            StringBuilder sb = new StringBuilder(allowIP);

            //如果用户配置的IP配置了多个,但没有以分号结尾,这里就给它加上分号
            if(!allowIP.endsWith(";")){
                sb.append(";");
            }
            //如果不匹配
            if(!pattern.matcher(sb).matches()){
                return false;
            }
        }
        return true;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        //1.获取访问者的IP地址
        String remoteAddr = request.getRemoteAddr();

        if(null == remoteAddr || remoteAddr.trim().equals("")){
            throw new RuntimeException("IP地址为空,拒绝访问!");
        }

        //如果白名单为空,则认为没做限制,放行
        if(null == allowRegexList || allowRegexList.size() == 0){
            filterChain.doFilter(request, response);
            return;
        }

        //检查用户IP是否在白名单
        if(checkIp(remoteAddr)){
            filterChain.doFilter(request, response);
            return;
        }else{
            throw new RuntimeException("您的IP:"+remoteAddr+",不在白名单中,拒绝访问!");
        }
    }

    //检查用户IP是否在白名单
    public boolean checkIp(String remoteAddr){
        for(String regex : allowRegexList){
            if(remoteAddr.matches(regex)){
                return true;
            }
        }
        return false;
    }

    @Override
    public void destroy() {

    }
}

三.备注

  本文比较不好理解的就是对第三种IP配置方式的解析,我的程序是把第三中配置的IP白名单解析为正则表达式的,比如用户配置的是192.168.1.,我并不是把它解析为192.168.1.0到192.168.1.255的256个IP地址,然后放到List集合中,而是吧192.168.1.解析为一条正则表达式192.168.1.(1\d{1,2}|2[0-4]\d|25[0-5]|\d{1,2}) 这条正则表达式可以匹配192.168.1.0到192.168.1.255的256个IP地址,这样就大大减少了程序循环的次数,提高了程序的性能.但由于我这里存放的是正则,所以我干脆就把我前面定义的那个存放白名单的List集合理解为,它里面存放的每一条都是正则,匹配白名单的正则.无论他是一个IP地址,如192.168.1.1我也当它是正则.所以我上面是这样校验用户IP是否在白名单中:

    //检查用户IP是否在白名单
    public boolean checkIp(String remoteAddr){
        //把白名单列表中的每一条都当成正则来匹配
        for(String regex : allowRegexList){
            if(remoteAddr.matches(regex)){
                return true;
            }
        }
        return false;
    }

  最后,其实对于第二中配置方式中配置的白名单列表,我也想把它变为正则表达式的,如192.168.1.10-192.168.1.20这样一条配置,我应该为它生成一条匹配192.168.1.10到192.168.1.20这11条ip的正则表达式,然后放到List集合中去的.而不是,用字符串切割,生成11条IP地址然后放到List集合中去的,这样太费劲了.但我确实想不出怎么实现才好,所以如果看了这篇文章的你,知道怎么实现,请在下方评论区告诉我解决方案,或者发邮件到我邮箱: kaifahm@163.com 我将感激不尽!

—————————— 本文结束 ——————————–

PS:

  • 本文属于本人原创文章,转载请注明出处,如果你觉得这篇文章对你有帮助或启发,也可以来请我喝咖啡。
  • 利益相关:本篇文章所有提到的商业品牌或网站链接均为笔者随意想到的或日常使用的,无任何广告费用。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号