|
@@ -18,6 +18,7 @@ package org.springframework.security.web.util.matcher;
|
|
|
|
|
|
import java.net.InetAddress;
|
|
|
import java.net.UnknownHostException;
|
|
|
+import java.util.Objects;
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
@@ -33,16 +34,17 @@ import org.springframework.util.StringUtils;
|
|
|
* IPv4 address will never match a request which returns an IPv6 address, and vice-versa.
|
|
|
*
|
|
|
* @author Luke Taylor
|
|
|
+ * @author Steve Riesenberg
|
|
|
* @since 3.0.2
|
|
|
*/
|
|
|
public final class IpAddressMatcher implements RequestMatcher {
|
|
|
|
|
|
private static Pattern IPV4 = Pattern.compile("\\d{0,3}.\\d{0,3}.\\d{0,3}.\\d{0,3}(/\\d{0,3})?");
|
|
|
|
|
|
- private final int nMaskBits;
|
|
|
-
|
|
|
private final InetAddress requiredAddress;
|
|
|
|
|
|
+ private final int nMaskBits;
|
|
|
+
|
|
|
/**
|
|
|
* 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).
|
|
@@ -52,18 +54,22 @@ public final class IpAddressMatcher implements RequestMatcher {
|
|
|
public IpAddressMatcher(String ipAddress) {
|
|
|
Assert.hasText(ipAddress, "ipAddress cannot be empty");
|
|
|
assertNotHostName(ipAddress);
|
|
|
+
|
|
|
+ String requiredAddress;
|
|
|
+ int nMaskBits;
|
|
|
if (ipAddress.indexOf('/') > 0) {
|
|
|
- String[] addressAndMask = StringUtils.split(ipAddress, "/");
|
|
|
- ipAddress = addressAndMask[0];
|
|
|
- this.nMaskBits = Integer.parseInt(addressAndMask[1]);
|
|
|
+ String[] parts = Objects.requireNonNull(StringUtils.split(ipAddress, "/"));
|
|
|
+ requiredAddress = parts[0];
|
|
|
+ nMaskBits = Integer.parseInt(parts[1]);
|
|
|
}
|
|
|
else {
|
|
|
- this.nMaskBits = -1;
|
|
|
+ requiredAddress = ipAddress;
|
|
|
+ nMaskBits = -1;
|
|
|
}
|
|
|
- this.requiredAddress = parseAddress(ipAddress);
|
|
|
- String finalIpAddress = ipAddress;
|
|
|
+ this.requiredAddress = parseAddress(requiredAddress);
|
|
|
+ this.nMaskBits = nMaskBits;
|
|
|
Assert.isTrue(this.requiredAddress.getAddress().length * 8 >= this.nMaskBits, () -> String
|
|
|
- .format("IP address %s is too short for bitmask of length %d", finalIpAddress, this.nMaskBits));
|
|
|
+ .format("IP address %s is too short for bitmask of length %d", requiredAddress, this.nMaskBits));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -71,14 +77,14 @@ public final class IpAddressMatcher implements RequestMatcher {
|
|
|
return matches(request.getRemoteAddr());
|
|
|
}
|
|
|
|
|
|
- public boolean matches(String address) {
|
|
|
+ public boolean matches(String ipAddress) {
|
|
|
// Do not match null or blank address
|
|
|
- if (!StringUtils.hasText(address)) {
|
|
|
+ if (!StringUtils.hasText(ipAddress)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- assertNotHostName(address);
|
|
|
- InetAddress remoteAddress = parseAddress(address);
|
|
|
+ assertNotHostName(ipAddress);
|
|
|
+ InetAddress remoteAddress = parseAddress(ipAddress);
|
|
|
if (!this.requiredAddress.getClass().equals(remoteAddress.getClass())) {
|
|
|
return false;
|
|
|
}
|
|
@@ -88,26 +94,31 @@ public final class IpAddressMatcher implements RequestMatcher {
|
|
|
byte[] remAddr = remoteAddress.getAddress();
|
|
|
byte[] reqAddr = this.requiredAddress.getAddress();
|
|
|
int nMaskFullBytes = this.nMaskBits / 8;
|
|
|
- byte finalByte = (byte) (0xFF00 >> (this.nMaskBits & 0x07));
|
|
|
for (int i = 0; i < nMaskFullBytes; i++) {
|
|
|
if (remAddr[i] != reqAddr[i]) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
+ byte finalByte = (byte) (0xFF00 >> (this.nMaskBits & 0x07));
|
|
|
if (finalByte != 0) {
|
|
|
return (remAddr[nMaskFullBytes] & finalByte) == (reqAddr[nMaskFullBytes] & finalByte);
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- private void assertNotHostName(String ipAddress) {
|
|
|
- boolean isIpv4 = IPV4.matcher(ipAddress).matches();
|
|
|
- if (isIpv4) {
|
|
|
- return;
|
|
|
- }
|
|
|
- String error = "ipAddress " + ipAddress + " doesn't look like an IP Address. Is it a host name?";
|
|
|
- Assert.isTrue(ipAddress.charAt(0) == '[' || ipAddress.charAt(0) == ':'
|
|
|
- || (Character.digit(ipAddress.charAt(0), 16) != -1 && ipAddress.contains(":")), error);
|
|
|
+ private static void assertNotHostName(String ipAddress) {
|
|
|
+ Assert.isTrue(isIpAddress(ipAddress),
|
|
|
+ () -> String.format("ipAddress %s doesn't look like an IP Address. Is it a host name?", ipAddress));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean isIpAddress(String ipAddress) {
|
|
|
+ // @formatter:off
|
|
|
+ return IPV4.matcher(ipAddress).matches()
|
|
|
+ || ipAddress.charAt(0) == '['
|
|
|
+ || ipAddress.charAt(0) == ':'
|
|
|
+ || Character.digit(ipAddress.charAt(0), 16) != -1
|
|
|
+ && ipAddress.indexOf(':') > 0;
|
|
|
+ // @formatter:on
|
|
|
}
|
|
|
|
|
|
private InetAddress parseAddress(String address) {
|