我需要确定给定的 IP 地址是否来自某个特殊网络,以便自动进行身份验证。
spring-security-web
的IpAddressMatcher。与 Apache Commons Net 不同,它同时支持 ipv4 和 ipv6。
import org.springframework.security.web.util.matcher.IpAddressMatcher;
...
private void checkIpMatch() {
matches("192.168.2.1", "192.168.2.1"); // true
matches("192.168.2.1", "192.168.2.0/32"); // false
matches("192.168.2.5", "192.168.2.0/24"); // true
matches("92.168.2.1", "fe80:0:0:0:0:0:c0a8:1/120"); // false
matches("fe80:0:0:0:0:0:c0a8:11", "fe80:0:0:0:0:0:c0a8:1/120"); // true
matches("fe80:0:0:0:0:0:c0a8:11", "fe80:0:0:0:0:0:c0a8:1/128"); // false
matches("fe80:0:0:0:0:0:c0a8:11", "192.168.2.0/32"); // false
}
private boolean matches(String ip, String subnet) {
IpAddressMatcher ipAddressMatcher = new IpAddressMatcher(subnet);
return ipAddressMatcher.matches(ip);
}
前一部分中的代码工作得很好,但需要包含
spring-security-web
。
如果您不愿意在项目中包含 Spring 框架,您可以使用这个类,它是 Spring 原始类的稍微修改版本,因此它没有非 JRE 依赖项。
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Matches a request based on IP Address or subnet mask matching against the remote
* address.
* <p>
* Both IPv6 and IPv4 addresses are supported, but a matcher which is configured with an
* IPv4 address will never match a request which returns an IPv6 address, and vice-versa.
*
* @author Luke Taylor
* @since 3.0.2
*
* Slightly modified by omidzk to have zero dependency to any frameworks other than the JRE.
*/
public final class IpAddressMatcher {
private final int nMaskBits;
private final InetAddress requiredAddress;
/**
* Takes a specific IP address or a range specified using the IP/Netmask (e.g.
* 192.168.1.0/24 or 202.24.0.0/14).
*
* @param ipAddress the address or range of addresses from which the request must
* come.
*/
public IpAddressMatcher(String ipAddress) {
if (ipAddress.indexOf('/') > 0) {
String[] addressAndMask = ipAddress.split("/");
ipAddress = addressAndMask[0];
nMaskBits = Integer.parseInt(addressAndMask[1]);
}
else {
nMaskBits = -1;
}
requiredAddress = parseAddress(ipAddress);
assert (requiredAddress.getAddress().length * 8 >= nMaskBits) :
String.format("IP address %s is too short for bitmask of length %d",
ipAddress, nMaskBits);
}
public boolean matches(String address) {
InetAddress remoteAddress = parseAddress(address);
if (!requiredAddress.getClass().equals(remoteAddress.getClass())) {
return false;
}
if (nMaskBits < 0) {
return remoteAddress.equals(requiredAddress);
}
byte[] remAddr = remoteAddress.getAddress();
byte[] reqAddr = requiredAddress.getAddress();
int nMaskFullBytes = nMaskBits / 8;
byte finalByte = (byte) (0xFF00 >> (nMaskBits & 0x07));
// System.out.println("Mask is " + new sun.misc.HexDumpEncoder().encode(mask));
for (int i = 0; i < nMaskFullBytes; i++) {
if (remAddr[i] != reqAddr[i]) {
return false;
}
}
if (finalByte != 0) {
return (remAddr[nMaskFullBytes] & finalByte) == (reqAddr[nMaskFullBytes] & finalByte);
}
return true;
}
private InetAddress parseAddress(String address) {
try {
return InetAddress.getByName(address);
}
catch (UnknownHostException e) {
throw new IllegalArgumentException("Failed to parse address" + address, e);
}
}
}
注意:请注意,使用此选项时,您有责任仔细检查许可证,以确保使用此代码时不会违反上述许可证规定的任何条款。 (当然,我将这段代码发布到 Stackoverflow.com 并不是违规。)
您还可以尝试以下针对 IPv4 的操作,或针对 IPv6 的等效操作。 注意:IP 是 32 位地址
int
boolean inSubnet = (ip & netmask) == (subnet & netmask);
或更短
boolean inSubnet = (ip ^ subnet) & netmask == 0;
开源 IPAddress Java 库将以多态方式针对 IPv4 和 IPv6 执行此操作并处理子网。免责声明:我是该图书馆的项目经理。
示例代码:
contains("10.10.20.0/30", "10.10.20.3");
contains("10.10.20.0/30", "10.10.20.5");
contains("1::/64", "1::1");
contains("1::/64", "2::1");
contains("1::3-4:5-6", "1::4:5");
contains("1-2::/64", "2::");
contains("bla", "foo");
static void contains(String network, String address) {
IPAddressString one = new IPAddressString(network);
IPAddressString two = new IPAddressString(address);
System.out.println(one + " contains " + two + " " + one.contains(two));
}
输出:
10.10.20.0/30 contains 10.10.20.3 true
10.10.20.0/30 contains 10.10.20.5 false
1::/64 contains 1::1 true
1::/64 contains 2::1 false
1::3-4:5-6 contains 1::4:5 true
1-2::/64 contains 2:: true
bla contains foo false
这是一个适用于 IPv4 和 IPv6 的版本,一个带前缀,一个带网络掩码。
/**
* Check if IP is within an Subnet defined by Network Address and Network Mask
* @param ip
* @param net
* @param mask
* @return
*/
public static final boolean isIpInSubnet(final String ip, final String net, final int prefix) {
try {
final byte[] ipBin = java.net.InetAddress.getByName(ip ).getAddress();
final byte[] netBin = java.net.InetAddress.getByName(net ).getAddress();
if(ipBin.length != netBin.length ) return false;
int p = prefix;
int i = 0;
while(p>=8) { if(ipBin[i] != netBin[i] ) return false; ++i; p-=8; }
final int m = (65280 >> p) & 255;
if((ipBin[i] & m) != (netBin[i]&m) ) return false;
return true;
} catch(final Throwable t) {
return false;
}
}
/**
* Check if IP is within an Subnet defined by Network Address and Network Mask
* @param ip
* @param net
* @param mask
* @return
*/
public static final boolean isIpInSubnet(final String ip, final String net, final String mask) {
try {
final byte[] ipBin = java.net.InetAddress.getByName(ip ).getAddress();
final byte[] netBin = java.net.InetAddress.getByName(net ).getAddress();
final byte[] maskBin = java.net.InetAddress.getByName(mask).getAddress();
if(ipBin.length != netBin.length ) return false;
if(netBin.length != maskBin.length) return false;
for(int i = 0; i < ipBin.length; ++i) if((ipBin[i] & maskBin[i]) != (netBin[i] & maskBin[i])) return false;
return true;
} catch(final Throwable t) {
return false;
}
}
我知道这是一个非常老的问题,但是当我寻求解决同样的问题时我偶然发现了这个问题。
有一个 commons-ip-math 库,我相信它做得非常好。请注意,截至 2019 年 5 月,该库还没有任何更新(可能是它已经非常成熟的库)。它在 maven-central
上可用它支持使用 IPv4 和 IPv6 地址。他们的简短文档包含有关如何检查地址是否在 IPv4 和 IPv6
的特定范围内的示例IPv4 范围检查示例:
String input1 = "192.168.1.0";
Ipv4 ipv41 = Ipv4.parse(input1);
// Using CIDR notation to specify the networkID and netmask
Ipv4Range range = Ipv4Range.parse("192.168.0.0/24");
boolean result = range.contains(ipv41);
System.out.println(result); //false
String input2 = "192.168.0.251";
Ipv4 ipv42 = Ipv4.parse(input2);
// Specifying the range with a start and end.
Ipv4 start = Ipv4.of("192.168.0.0");
Ipv4 end = Ipv4.of("192.168.0.255");
range = Ipv4Range.from(start).to(end);
result = range.contains(ipv42); //true
System.out.println(result);
为了检查子网中的 IP,我使用了 SubnetUtils 类中的 isInRange 方法。但是这个方法有一个bug,如果你的子网是X,那么每个低于X的IP地址,isInRange都会返回true。例如,如果您的子网为 10.10.30.0/24,并且您想要检查 10.10.20.5,则此方法返回 true。为了处理这个错误,我使用了下面的代码。
public static void main(String[] args){
String list = "10.10.20.0/24";
String IP1 = "10.10.20.5";
String IP2 = "10.10.30.5";
SubnetUtils subnet = new SubnetUtils(list);
SubnetUtils.SubnetInfo subnetInfo = subnet.getInfo();
if(MyisInRange(subnetInfo , IP1) == true)
System.out.println("True");
else
System.out.println("False");
if(MyisInRange(subnetInfo , IP2) == true)
System.out.println("True");
else
System.out.println("False");
}
private boolean MyisInRange(SubnetUtils.SubnetInfo info, String Addr )
{
int address = info.asInteger( Addr );
int low = info.asInteger( info.getLowAddress() );
int high = info.asInteger( info.getHighAddress() );
return low <= address && address <= high;
}