【问题标题】:Binding CXF Client source IP address绑定CXF Client源IP地址
【发布时间】:2016-12-05 23:17:33
【问题描述】:

我有一个连接到 Web 服务的 CXF 客户端。此客户端安装在同一网络中具有两个 IP 地址(例如 172.16.1.101 和 172.16.1.102)的计算机中。

如何将 CXF 客户端配置为使用特定的源 IP 地址,以便服务器看到来自该特定 IP 地址而不是其他 IP 地址的请求?

如果我可以访问 Socket,我会执行以下操作:

Socket s = new Socket(); 
s.bind(new InetSocketAddress("172.16.1.102", 0));  //this Ip address is the one I need to specify
s.connect(new InetSocketAddress("google.com", 80));

是否可以配置 CXF 创建的 Sockets 以便我可以指定源 IP 地址?

编辑:我需要指定源 IP 地址,因为在客户端和 Web 服务器之间有一个防火墙,其中一个 IP 地址具有规则(来自另一个 IP 地址的连接被阻止)。

【问题讨论】:

    标签: java web-services sockets cxf


    【解决方案1】:

    CXF 客户端使用java.net.URLConnection 连接到服务。可以通过这种方式配置 URLConnection 选择本地 IP 地址(见How can I specify the local address on a java.net.URLConnection?

    URL url = new URL(yourUrlHere);
    Proxy proxy = new Proxy(Proxy.Type.DIRECT, 
        new InetSocketAddress( 
            InetAddress.getByAddress(
                new byte[]{your, ip, interface, here}), yourTcpPortHere));
    URLConnection conn = url.openConnection(proxy);
    

    我检查了工件 cxf-rt-rs-clientcxf-rt-transports-http 的代码以了解 CXF 如何创建连接。在ProxyFactory 中是用于创建UrlConnection 所需的Proxy 对象的代码

    private Proxy createProxy(final HTTPClientPolicy policy) {
        return new Proxy(Proxy.Type.valueOf(policy.getProxyServerType().toString()),
                         new InetSocketAddress(policy.getProxyServer(),
                                               policy.getProxyServerPort()));
    }
    

    如您所见,没有办法配置IP地址,所以问题的答案恐怕是不能用CXF配置源IP地址

    但是,我认为修改源代码以允许设置源IP地址并不难

    HTTPClientPolicy

    将以下代码添加到org.apache.cxf.transports.http.configuration.HTTPClientPolicycxf-rt-transports-http

    public class HTTPClientPolicy {
        protected byte[] sourceIPAddress;
        protected int port;
    
        public boolean isSetSourceIPAddress(){
            return (this.sourceIPAddress != null);
        }
    

    代理工厂

    将以下代码修改为org.apache.cxf.transport.http.ProxyFactorycxf-rt-transports-http

     //added || policy.isSetSourceIPAddress()
     //getProxy() calls finally to createProxy
     public Proxy createProxy(HTTPClientPolicy policy, URI currentUrl) {
        if (policy != null) {
            // Maybe the user has provided some proxy information
            if (policy.isSetProxyServer() || policy.isSetSourceIPAddress())
                && !StringUtils.isEmpty(policy.getProxyServer())) {
                return getProxy(policy, currentUrl.getHost());
            } else {
                // There is a policy but no Proxy configuration,
                // fallback on the system proxy configuration
                return getSystemProxy(currentUrl.getHost());
            }
        } else {
            // Use system proxy configuration
            return getSystemProxy(currentUrl.getHost());
        }
    }
    
    //Added condition to set the source IP address (is set)
    //Will work also with a proxy
    private Proxy createProxy(final HTTPClientPolicy policy) {
        if (policy.isSetSourceIPAddress()){
            Proxy proxy = new Proxy(Proxy.Type.DIRECT, 
                new InetSocketAddress( 
                    InetAddress.getByAddress(
                        policy.getSourceIPAddress(), policy.getPort()));
        } else {
            return new Proxy(Proxy.Type.valueOf(policy.getProxyServerType().toString()),
                         new InetSocketAddress(policy.getProxyServer(),
                                               policy.getProxyServerPort()));
        }                
    }
    

    用法

    Client client = ClientProxy.getClient(service);
    HTTPConduit http = (HTTPConduit) client.getConduit();
    HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
    httpClientPolicy.setSourceIPAddress(new byte[]{your, ip, interface, here}));
    httpClientPolicy.setPort(yourTcpPortHere);
    http.setClient(httpClientPolicy);
    

    【讨论】:

    • 看着你的回答我想也许是httpClientPolicy。 setProxyServer(...) 可以工作(无需修改 CXF),因为原始 createProxy() 使用它。但它不起作用,因为无法设置 Proxy.Type.DIRECT 并且 proxyServer 用于 ProxyFactory 的其他部分(设置实际代理)。所以,你的答案是完全正确的,设置源IP地址的唯一方法是修改CXF源代码。这是一个非常完整的答案。谢谢!
    • 我尝试按照您的建议修改 CXF 代码,但它不起作用。无法使用 Proxy.Type.DIRECT 创建新的代理。如果您查看code of Proxy,如果参数为 DIRECT,则会引发异常。另请参阅How can I specify the local address on a java.net.URLConnection? 答案中的评论
    • 你是绝对正确的。自 JDK1.5 以来,Proxy 类总是抛出类型为 DIRECT 的异常(我没有查看更多版本)。 stackoverflow.com/questions/91678/… 中链接的代码不起作用。我试图继承Proxy,但URLConnection 克隆了代理并失败了。
    • 我认为可以将第二个答案应用于在org.apache.cxf.transport.https.HttpsURLConnectionFactory.createConnection 打开套接字的 HTTPS 连接,但该解决方案似乎不太可靠。在这个问题stackoverflow.com/questions/1721444/… 他们建议在机器本身上使用代理或注册一个自定义URLStreamHandlerFactory 来操作套接字。复杂的。 :( 我会查看答案
    【解决方案2】:

    自定义 URLStreamHandlerFactory 可以工作。

    例子:

            ...
            URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
                @Override
                public URLStreamHandler createURLStreamHandler(String protocol) {
                    if (protocol.equals("http")) {
                        return new HttpHandler();
                    } else if (protocol.equals("https")) {
                        return new sun.net.www.protocol.https.Handler();
                    }
                    return null;
                }
            });
            ...
    
    private class HttpHandler extends java.net.URLStreamHandler {
    
        protected int getDefaultPort() {
            return 80;
        }
    
        @Override
        protected URLConnection openConnection(URL u) throws IOException {
            return openConnection(u, (Proxy) null);
        }
    
        @Override
        protected URLConnection openConnection(URL u, Proxy p) throws IOException {
            return new HttpURLConnection(u, p) {
    
                @Override
                protected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout)
                        throws IOException {
                    return new HttpClient(url, (String) null, -1, true, connectTimeout) {
    
                        @Override
                        protected Socket createSocket() throws IOException {
                            Socket s = new Socket();
    
                            s.bind(new InetSocketAddress(InetAddress.getByName("1.2.3.4"),
                                    0)); // yours IP here
    
                            return s;
                        }
    
                    };
                }
    
                @Override
                protected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout,
                        boolean useCache) throws IOException {
                    return getNewHttpClient(url, p, connectTimeout);
                }
    
            };
        }
    }
    

    对于 HTTPS 也可以使用自定义 SSLSocketFactory

    HttpsURLConnection.setDefaultSSLSocketFactory(...);
    

    【讨论】:

    • 这是正确答案,因为这是唯一有效的答案。抱歉,我已经将bonty分配给了另一个。一个重要的细节是,为了使 https 工作,您需要设置 setUseHttpsURLConnectionDefaultSslSocketFactory(true)(在 HTTPConduit TLSClientParameters 中)。
    【解决方案3】:

    不确定我是否正确理解了您的问题,但我认为您不需要在客户端上专门设置 IP 地址以供服务器读取`

    Message message = PhaseInterceptorChain.getCurrentMessage();
    HttpServletRequest request = (HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST);
    request.getRemoteAddr()
    

    在服务器上添加此代码,它实际上可以从请求中找到 IP,您可以在服务器上对 IP 进行比较。让我知道这是否是您正在寻找的。​​p>

    编辑:-----

    嗯,我的理解是防火墙在消息中看不到 Header 或 Payload 中的 IP。它检查原始服务器的 IP。

    如果我错了,请纠正我,但我觉得如果您将客户端部署在添加了防火墙规则的服务器上并调用服务器,它没有理由不工作。

    让我知道你遇到了什么样的错误,比如堆栈跟踪或其他什么,我们可以看到发生了什么。

    【讨论】:

    • 我编辑了这个问题。我需要指定源 IP 地址,因为在客户端和 Web 服务器之间有一个防火墙,其中包含一个 IP 地址而不是另一个 IP 地址的规则。如果连接不是来自正确的 IP 地址,则不会通过防火墙
    • 您是对的,防火墙查看的是 IP 标头,而不是消息的有效负载。这就是为什么我必须在 Socket 中设置 IP。我从客户端得到的错误是超时错误,因为防火墙没有转发数据包。如果一台机器有两个相同网络的IP地址,你可以配置打开每个socket时使用哪一个。 CXF 框架负责一切,包括套接字(和源 IP 地址),但我需要一种方法来配置这个套接字(如果可能的话)。
    猜你喜欢
    • 2014-05-16
    • 2012-06-15
    • 1970-01-01
    • 1970-01-01
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-16
    相关资源
    最近更新 更多