class IpExtractor implements CredentialsExtractor {

    private List<String> alternateIpHeaders = Collections.emptyList();

    @Getter
    private String proxyIp = Pac4jConstants.EMPTY_STRING;

    /**
     * <p>Constructor for IpExtractor.</p>
     */
    public IpExtractor() {}

    /**
     * <p>Constructor for IpExtractor.</p>
     *
     * @param alternateIpHeaders a {@link String} object
     */
    public IpExtractor(String... alternateIpHeaders) {
        this.alternateIpHeaders = Arrays.asList(alternateIpHeaders);
    }

    /** {@inheritDoc} */
    @Override
    public Optional<Credentials> extract(final CallContext ctx) {<FILL_FUNCTION_BODY>}

    private Optional<String> ipFromHeaders(WebContext context) {
        Optional<String> ip;
        for (var header : alternateIpHeaders) {
            ip = context.getRequestHeader(header);
            if (ip.isPresent() && !ip.get().isEmpty()) {
                return ip;
            }
        }
        return Optional.empty();
    }

    /**
     * <p>Setter for the field <code>proxyIp</code>.</p>
     *
     * @param proxyIp Set the IP to verify the proxy request source.
     *               Setting {@code null} or {@code ""} (empty string) disabled the proxy IP check.
     */
    public void setProxyIp(String proxyIp) {
        this.proxyIp = proxyIp == null ? Pac4jConstants.EMPTY_STRING : proxyIp;
    }

    /**
     * <p>Setter for the field <code>alternateIpHeaders</code>.</p>
     *
     * @param alternateIpHeaders Sets alternate headers to search for IP.
     *                           The first match will be returned as specified for {@code enhanced for} iteration over arrays.
     */
    public void setAlternateIpHeaders(final String... alternateIpHeaders) {
        CommonHelper.assertNotNull("alternateIpHeaders", alternateIpHeaders);
        this.alternateIpHeaders = Arrays.asList(alternateIpHeaders);
    }
}

        val webContext = ctx.webContext();
        final Optional<String> ip;
        if (alternateIpHeaders.isEmpty()) {
            ip = Optional.ofNullable(webContext.getRemoteAddr());
        } else {
            val requestSourceIp = webContext.getRemoteAddr();
            if (this.proxyIp.isEmpty()) {
                ip = ipFromHeaders(webContext);
            }
            // if using proxy, check if the ip proxy is correct
            else if (this.proxyIp.equals(requestSourceIp)) {
                ip = ipFromHeaders(webContext);
            } else {
                ip = Optional.empty();
            }
        }

        if (ip.isEmpty()) {
            return Optional.empty();
        }

        return Optional.of(new TokenCredentials(ip.get()));
    
<no_super_class>