How to find users' IPs in Spring Security?

2020-02-17 10:38发布

问题:

I need to find those user who are logged in our application.
We are using Spring Security and there must be a way to find out users' IPs.

I think these information are stored in their sessions. In Spring Security, the current sessions are stored in SessionRegistry. From this class I can have list of authenticated users and some session information. (Using getAllPrincipals , getAllSessions and getSessionInformation)

The question is, how can I have access to current users' IPs? Consider we have to give service to a known region only.
The SessionInformation is not much help as it does not contain much information.

回答1:

I think that the check be achieved by using hasIpAddress http expression

See section 15.2 Web Security Expressions

<http use-expressions="true">
    <intercept-url pattern="/admin*"
        access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
    ...
  </http>

If you want more flexibility, you can implement your own IP address check service, based on IpAddressMatcher:

<bean id="ipCheckService" class="my.IpCheckService">
</bean>

<security:http auto-config="false" access-denied-page="/accessDenied.jsp" 
use-expressions="true">
    <security:intercept-url pattern="/login.jsp"
        access="@ipCheckService.isValid(request)" />

bean implementation:

public class IpCheckService {
    public boolean isValid(HttpServletRequest request) {
        //This  service is a bean so you can inject other dependencies,
            //for example load the white list of IPs from the database 
        IpAddressMatcher matcher = new IpAddressMatcher("192.168.1.0/24");

    try {
        return matcher.matches(request);
    } catch (UnsupportedOperationException e) { 
        return false;
    }
    }
}

update: you can try to get current user IP this way:

    public static String getRequestRemoteAddr(){
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes())
                   .getRequest(); 
        return request.getRemoteAddr();
}

update The information about the relation between IP addresses and sessions can only be gathered from the different sources(like listening to AuthenticationSuccessEvent and SessionDestroyedEvent events, implementing a filter or using an AOP interceptor). Spring Security doesn't store such information because it's useless, as IP address has some meaning only while the server is processing a ServletRequest.

IP address may change(user may be using a proxy), so we can only audit different kinds of events like logging in with some credentials, accessing a service from a different IP, or doing some suspicious activity.



回答2:

You can get IP address from WebAuthenticationDetails object, which can be obtained from Authentication instance.

Object details =
    SecurityContextHolder.getContext().getAuthentication().getDetails();
if (details instanceof WebAuthenticationDetails)
    ipAddress = ((WebAuthenticationDetails) details).getRemoteAddress();


回答3:

You can use HttpServletRequest for getting user's IP address. (Developers of SpringSecurity do this in the same way in their expression hasIpAddress(...) that is placed in WebSecurityExpressionRoot class).

For example you can get HttpServletRequest in 2 ways:

1) Using RequestContextHolder:

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();

2) Using autowiring:

@Autowired
private HttpServletRequest request;

I took this from here.

Then using HttpServletRequest you can get Ip address in such way:

String address = request.getRemoteAddr();

And here how addresses are compared in spring security:

/**
 * Takes a specific IP address or a range 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.
 * @return true if the IP address of the current request is in the required range.
 */
public boolean hasIpAddress(String ipAddress) {
    return (new IpAddressMatcher(ipAddress).matches(request));
}

And IpAddressMatcher class:

public final class IpAddressMatcher implements RequestMatcher {
    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 = StringUtils.split(ipAddress, "/");
            ipAddress = addressAndMask[0];
            nMaskBits = Integer.parseInt(addressAndMask[1]);
        } else {
            nMaskBits = -1;
        }
        requiredAddress = parseAddress(ipAddress);
    }

    public boolean matches(HttpServletRequest request) {
        return matches(request.getRemoteAddr());
    }

    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 oddBits = nMaskBits % 8;
        int nMaskBytes = nMaskBits/8 + (oddBits == 0 ? 0 : 1);
        byte[] mask = new byte[nMaskBytes];

        Arrays.fill(mask, 0, oddBits == 0 ? mask.length : mask.length - 1, (byte)0xFF);

        if (oddBits != 0) {
            int finalByte = (1 << oddBits) - 1;
            finalByte <<= 8-oddBits;
            mask[mask.length - 1] = (byte) finalByte;
        }

 //       System.out.println("Mask is " + new sun.misc.HexDumpEncoder().encode(mask));

        for (int i=0; i < mask.length; i++) {
            if ((remAddr[i] & mask[i]) != (reqAddr[i] & mask[i])) {
                return false;
            }
        }

        return true;
    }

    private InetAddress parseAddress(String address) {
        try {
            return InetAddress.getByName(address);
        } catch (UnknownHostException e) {
            throw new IllegalArgumentException("Failed to parse address" + address, e);
        }
    }
}

EDIT:

According to related questions here and here you can add user's IP to the session using custom filter. And then get this information from session related to the user where it will be necessary. For example you can put user's IP info like this:

public class MonitoringFilter extends GenericFilterBean{
@Override
public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;

    String userIp = httpRequest.getRemoteAddr();

    httpRequest.getSession().setAttribute("userIp", userIp);

    // Add other attributes to session if necessary
}