基於java過濾器實現web系統的IP訪問控制
阿新 • • 發佈:2019-02-09
一.使用場景
一般情況下,我們設計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<String> allowRegexList = new ArrayList<String>();
@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() {
}
}
更多資訊,請關注http://www.legendshop.cn
三.備註
本文比較不好理解的就是對第三種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是否在白名單中:
一般情況下,我們設計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<String> allowRegexList = new ArrayList<String>();
@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() {
}
}
更多資訊,請關注http://www.legendshop.cn
三.備註
本文比較不好理解的就是對第三種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; }
原文連結:http://www.bijishequ.com/detail/410522?p=