diff --git a/README.md b/README.md index 8e913e802401bc437319dab9e7bebf1129df6f32..e1d7205fdbb26953b7bd99b41dc167329f464944 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,15 @@ Java Forward Proxy,jfp,基于java实现正向代理,已实现HTTPS和HTTP 正向代理中的客户端知道目标服务器的地址,目标服务器不知道实际客户端,只知道哪个代理服务器访问了它,即正向代理可以对目标服务器屏蔽或隐藏客户端的信息。 -比如你电脑想访问的一个受限网站(https://www.demo.com),但是你的网络有限制,无法直接访问这个网站(位于某个服务器),你可以通过如下步骤实现正向代理访问: +比如你电脑想访问的一个受限网站(https://www.limit.com),但是你的网络有限制,无法直接访问这个网站(位于某个服务器),你可以通过如下步骤实现正向代理访问: 1. 找一台可以访问这个网站的另外一台服务器(代理服务器,一般是带独立外网IP(域名)/端口的服务器),在上面部署该代理程序(配置好代理端口,启动程序,防火墙需要放开代理端口)。 -2. 你电脑如果是window,可以在"网络和Internet"里找到代理配置,配置代理服务器地址(IP/域名)和端口并启用,然后你电脑浏览器就可以直接输入受限网站(https://www.demo.com)访问。 +2. 你电脑如果是window,可以在"网络和Internet"里找到代理配置,配置代理服务器地址(IP/域名)和端口并启用,然后你电脑浏览器就可以直接输入受限网站(https://www.limit.com)访问。 3. 访问过程:window系统会发送请求到”地址(IP/域名)和端口“对应的代理服务器,代理服务器上代理程序通过服务中转方式访问受限网站转发返回数据到你电脑。 4. 即你电脑的请求都发到代理服务器的代理服务上,代理服务转发请求信息到目标服务器(受限网站)并转发返回数据到你电脑。 #### 软件架构 软件架构说明 -Spring Boot 2.7.14(运行控制、配置管理)+Vert.x 4.5.3(服务管理、代理) +Spring Boot运行控制、配置管理)+Vert.x(服务管理、代理) #### 安装教程 @@ -24,7 +24,7 @@ Spring Boot 2.7.14(运行控制、配置管理)+Vert.x 4.5.3(服务管理 3. linux下直接执行命令启动(其中最后的start参数会自动后台隐藏运行,如果不需要后台运行可以改为run参数或者去掉start参数):java -server -Dfile.encoding=utf-8 -Dspring.config.location=./application.yml -Xms512m -Xmx1024m -jar jfp-1.0.0-SNAPSHOT.jar start 4. windows下直接执行命令启动(其中最后的start参数会自动后台隐藏运行,如果不需要后台运行可以改为run参数或者去掉start参数):java -server -Dfile.encoding=utf-8 -Dspring.config.location=./application.yml -Xms512m -Xmx1024m -jar jfp-1.0.0-SNAPSHOT.jar 5. 确保代理服务器上的端口防火墙放行。 -6. linux下执行脚本停止服务[stop.sh](src/bin/stop.sh) +6. linux下如果是start参数启动的执行脚本停止服务[stop.sh](src/bin/stop.sh) 7. window下如果是start参数启动的,停止服务可以先通过java -jar jfp-1.0.0-SNAPSHOT.jar list查看vertxId,然后通过java -jar jfp-1.0.0-SNAPSHOT.jar stop vertxId停止。 8. window下如果最后是run参数或者jar后面不带run/start参数启动的,直接关闭cmd窗口或者ctr+c停止服务。 #### 使用说明 diff --git a/README_en.md b/README_en.md new file mode 100644 index 0000000000000000000000000000000000000000..5f9907c8d6b4e3c562a5d8d8260cf90d6e6e117f --- /dev/null +++ b/README_en.md @@ -0,0 +1,38 @@ +# jfp + +#### description +Java Forward Proxy, named jfp,use java to implement Forward Proxy. This project has implemented HTTPS and HTTP Forward Proxy. + +Forward proxy refers to a server located between the client and the origin server. +In order to retrieve content from the origin server, the client sends a request to the proxy and specifies the destination (origin server). +The proxy then forwards the request to the origin server and returns the retrieved content to the client, just allowing the client to use forward proxy. + +The client in the forward proxy knows the address of the target server, while the target server does not know the actual client and only knows which proxy server accessed it. +In other words, the forward proxy can block or hide client information from the target server. + +For example, A restricted website that your computer wants to access( https://www.limit.com )However, your network is limited, and you cannot directly access this website (located on a server). +You can achieve forward proxy access by following these steps: +1. Find a server (proxy server, usually a server with an independent external IP (domain name)/port) that can access this website, deploy the proxy program on it (configure the proxy port, start the program, and the firewall needs to release the proxy port). +2. If your computer is Windows, you can find proxy configuration in "Network and Internet", configure the proxy server address (IP/domain name) and port, and enable them. Then your computer browser can access restricted websites( https://www.limit.com )Visit. +3. Access process: The Windows system will send a request to the proxy server corresponding to the address (IP/domain name) and port. The proxy program on the proxy server accesses the restricted website through service forwarding and forwards the returned data to your computer. + +Overall:All requests from your computer are sent to the proxy service of the proxy server, which forwards the request information to the target server (restricted website) and returns the data to your computer. + +#### Software Architecture + +Spring Boot(Operation control and configuration management)+Vert.x(Service management and Forward Proxy) + +#### Installation tutorial + +1. Install JDK8+ or JRe8+ on the proxy server (with external IP and port) that requires the installation of the proxy program. +2. Upload the deployment package, modify the ports in the configuration file [application.yml](src/main/resources/application.yml), and then use the script [start.sh](src/bin/start.sh) to start the program under Linux. +3. Directly execute the command to start under Linux (where the last start parameter will automatically hide and run in the background. If background running is not required, you can change it to "run" parameter or remove the "start" parameter):java -server -Dfile.encoding=utf-8 -Dspring.config.location=./application.yml -Xms512m -Xmx1024m -jar jfp-1.0.0-SNAPSHOT.jar start +4. Directly execute the command to start under Windows (the last start parameter will automatically hide and run in the background. If background running is not required, you can change it to "run" parameter or remove the "start" parameter):java -server -Dfile.encoding=utf-8 -Dspring.config.location=./application.yml -Xms512m -Xmx1024m -jar jfp-1.0.0-SNAPSHOT.jar +5. Ensure port firewall clearance on proxy server. +6. Under Linux, if the execution script starts with the start parameter, it will stop the service by executing the script[stop.sh](src/bin/stop.sh) +7. If it is started with the start parameter under the window, stopping the service can be done first through "java -jar jfp-1.0.0-SNAPSHOT.jar list" to see the vertxId,and then stop the service by executing the cmd command "java -jar jfp-1.0.0-SNAPSHOT.jar stop vertxId" to stop it. +8. If the last parameter under the window is run or the jar is started without the run/start parameter, simply close the cmd window or CTR+C to stop the service. +#### instructions + +1. After ensuring that the port firewall on the proxy server is cleared, configure the proxy IP (domain name) and port on computers such as Windows. +2. Enter the local network restricted proxy server access unrestricted website address on the browser and surf happily. diff --git a/src/bin/start.bat b/src/bin/start.bat index e33ce6d54f0f560aefcdf4789c1e61208195051f..dad0f77368d384b6825b174b28d727596ad5b81a 100644 --- a/src/bin/start.bat +++ b/src/bin/start.bat @@ -1,4 +1,4 @@ chcp 65001 -cd D:\jrp-server +cd D:\jfp D: java -server -Dfile.encoding=utf-8 -Dspring.config.location=./application.yml -Xms512m -Xmx1024m -jar jfp-1.0.0-SNAPSHOT.jar \ No newline at end of file diff --git a/src/bin/start.sh b/src/bin/start.sh index ed9f6ef56a345c25144d433fa8ecbeab74ba43e1..00d99076a246975e67f98a6d3df90298fbb6e63c 100644 --- a/src/bin/start.sh +++ b/src/bin/start.sh @@ -1,8 +1,8 @@ #!/bin/bash -pid=`ps -ef | grep jrp-server-1.0.0-SNAPSHOT.jar | grep -v grep | awk '{print $2}'` +pid=`ps -ef | grep jfp-1.0.0-SNAPSHOT.jar | grep -v grep | awk '{print $2}'` if [ -n "$pid" ]; then kill -9 $pid fi java -server -Dfile.encoding=utf-8 -Dspring.config.location=./application.yml -Xms512m -Xmx1024m -jar jfp-1.0.0-SNAPSHOT.jar start>/dev/null 2>&1 & sleep 5 -tail -f log/jrp.log \ No newline at end of file +tail -f log/jfp.log \ No newline at end of file diff --git a/src/main/java/com/tony/jfp/core/ForwardProxyVerticle.java b/src/main/java/com/tony/jfp/core/ForwardProxyVerticle.java index a1059c63b5bdd1c9edccd5b69af24ab38ef7ab71..019646bb81682c93d46d1189a232c522d36aa46e 100644 --- a/src/main/java/com/tony/jfp/core/ForwardProxyVerticle.java +++ b/src/main/java/com/tony/jfp/core/ForwardProxyVerticle.java @@ -49,39 +49,51 @@ public class ForwardProxyVerticle extends AbstractVerticle { HostAndPort authority = request.authority(); String hostPort = authority.toString(); HttpServerResponse response = request.response(); - if (request.method() == HttpMethod.CONNECT) { - //https://www.cnblogs.com/wanghongwei-dev/p/18543399 - //客户端:发起请求的实体,它希望访问一个位于目标服务器上的资源。 - //代理服务器:位于客户端和目标服务器之间的中间实体,负责转发客户端的请求到目标服务器,并将目标服务器的响应返回给客户端。 - //目标服务器:提供客户端所需资源的服务器。 - //步骤 - //1.客户端发送 CONNECT 请求 - //客户端向代理服务器发送一个 CONNECT 请求。这个请求中包含了目标服务器的地址(如域名或 IP 地址)和端口号(通常是 HTTPS 服务的标准端口 443)。 - //例如,客户端可能发送一个请求来建立到 https://example.com 的连接,而 CONNECT 请求中实际包含的是 example.com 的 IP 地址和端口 443。 - //2.代理服务器处理 CONNECT 请求 - //代理服务器接收到 CONNECT 请求后,解析出目标服务器的地址和端口号。 - //代理服务器尝试建立一个到目标服务器的 TCP 连接。这个连接是在代理服务器和目标服务器之间建立的,而不是在客户端和目标服务器之间直接建立的。 - //3.TCP 连接建立 - //如果代理服务器成功建立了到目标服务器的 TCP 连接,它会返回一个 HTTP 200 状态码给客户端。 - //这个 HTTP 200 状态码是代理服务器对客户端 CONNECT 请求的响应,表示代理服务器已经成功建立了到目标服务器的连接。 - //4.数据传输 - //一旦 TCP 连接建立并且客户端收到了 HTTP 200 状态码,客户端和代理服务器之间的 HTTP 连接就被视为一个“隧道”。 - //在这个隧道中,客户端可以直接发送数据到代理服务器,而代理服务器会将这些数据原封不动地转发到目标服务器。 - //同样地,目标服务器的响应也会通过代理服务器原封不动地返回给客户端。 - //重要的是要注意,此时的数据传输不再遵循 HTTP 协议;相反,它是一个透明的 TCP 连接,可以传输任何类型的数据(包括 HTTPS 加密的数据)。 - //5.连接关闭 - //当客户端完成数据传输并希望关闭连接时,它会发送一个特殊的信号(如 TCP FIN 包)给代理服务器。 - //代理服务器接收到这个信号后,会关闭它与目标服务器之间的 TCP 连接。 - //同时,代理服务器也会向客户端发送一个响应(可能是 HTTP 响应或 TCP 关闭信号),表示连接已经关闭。 - NetClientOptions netClientOptions = new NetClientOptions().setSsl(request.isSSL()).setTcpKeepAlive(true).setConnectTimeout(1000).setIdleTimeout(1); + //https://www.cnblogs.com/wanghongwei-dev/p/18543399 + //客户端:发起请求的实体,它希望访问一个位于目标服务器上的资源。 + //代理服务器:位于客户端和目标服务器之间的中间实体,负责转发客户端的请求到目标服务器,并将目标服务器的响应返回给客户端。 + //目标服务器:提供客户端所需资源的服务器。 + //步骤 + //1.客户端发送 CONNECT 请求 + //客户端向代理服务器发送一个 CONNECT 请求。这个请求中包含了目标服务器的地址(如域名或 IP 地址)和端口号(通常是 HTTPS 服务的标准端口 443)。 + //例如,客户端可能发送一个请求来建立到 https://example.com 的连接,而 CONNECT 请求中实际包含的是 example.com 的 IP 地址和端口 443。 + //2.代理服务器处理 CONNECT 请求 + //代理服务器接收到 CONNECT 请求后,解析出目标服务器的地址和端口号。 + //代理服务器尝试建立一个到目标服务器的 TCP 连接。这个连接是在代理服务器和目标服务器之间建立的,而不是在客户端和目标服务器之间直接建立的。 + //3.TCP 连接建立 + //如果代理服务器成功建立了到目标服务器的 TCP 连接,它会返回一个 HTTP 200 状态码给客户端。 + //这个 HTTP 200 状态码是代理服务器对客户端 CONNECT 请求的响应,表示代理服务器已经成功建立了到目标服务器的连接。 + //4.数据传输 + //一旦 TCP 连接建立并且客户端收到了 HTTP 200 状态码,客户端和代理服务器之间的 HTTP 连接就被视为一个“隧道”。 + //在这个隧道中,客户端可以直接发送数据到代理服务器,而代理服务器会将这些数据原封不动地转发到目标服务器。 + //同样地,目标服务器的响应也会通过代理服务器原封不动地返回给客户端。 + //重要的是要注意,此时的数据传输不再遵循 HTTP 协议;相反,它是一个透明的 TCP 连接,可以传输任何类型的数据(包括 HTTPS 加密的数据)。 + //5.连接关闭 + //当客户端完成数据传输并希望关闭连接时,它会发送一个特殊的信号(如 TCP FIN 包)给代理服务器。 + //代理服务器接收到这个信号后,会关闭它与目标服务器之间的 TCP 连接。 + //同时,代理服务器也会向客户端发送一个响应(可能是 HTTP 响应或 TCP 关闭信号),表示连接已经关闭。 + if (canToNetSocket(request)) { + NetClientOptions netClientOptions = new NetClientOptions().setSsl(request.isSSL()).setTcpKeepAlive(true).setConnectTimeout(1000); + log.debug("tcp uri:{}", request.uri()); + log.debug("tcp authority:{}", authority); vertx.createNetClient(netClientOptions).connect(authority.port(), authority.host()).onSuccess(proxySocket -> { log.debug("请求[{}]代理连接成功!", hostPort); request.toNetSocket().onSuccess(socket -> { log.debug("请求[{}]转为TCP成功!", hostPort); socket.pause(); proxySocket.pause(); + socket.closeHandler(handler -> { + proxySocket.closeHandler(null); + proxySocket.close(); + }); + proxySocket.closeHandler(handler -> { + socket.closeHandler(null); + socket.close(); + }); socket.pipeTo(proxySocket); proxySocket.pipeTo(socket); + socket.resume(); + proxySocket.resume(); }).onFailure(error -> { proxySocket.close(); if (!request.response().ended()) { @@ -150,6 +162,15 @@ public class ForwardProxyVerticle extends AbstractVerticle { }); } + /** + * 判断是否可转为TCP请求 + * @param request 请求对象 + * @return true-可转为TCP请求,false-不可转为TCP请求 + */ + private static boolean canToNetSocket(HttpServerRequest request) { + return request.method() == HttpMethod.CONNECT || (request.method() == HttpMethod.GET && request.headers().contains(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE, true)); + } + @Override public void stop() { if (server != null) { diff --git a/src/main/java/com/tony/jfp/core/SecurityService.java b/src/main/java/com/tony/jfp/core/SecurityService.java index 70f2b8df190ddafdcf83ed129ad0db40faa2fe1e..bc66e8c5661a82cd436a7beac89bb64c24fe7c01 100644 --- a/src/main/java/com/tony/jfp/core/SecurityService.java +++ b/src/main/java/com/tony/jfp/core/SecurityService.java @@ -124,7 +124,7 @@ public class SecurityService implements InitializingBean { public String getWWWAuthenticate(String host) { String algorithm = properties.getAlgorithm(); String nonce = getNonce(host); - String authenticate = " Digest realm=\"jrp-auth@example.org\",qop=\"auth, auth-int\",algorithm=" + algorithm + ",nonce=\"" + nonce + "\",opaque=\"" + TokenUtils.runtimeToken + "\""; + String authenticate = " Digest realm=\"jfp-auth@example.org\",qop=\"auth, auth-int\",algorithm=" + algorithm + ",nonce=\"" + nonce + "\",opaque=\"" + TokenUtils.runtimeToken + "\""; return authenticate; } @@ -181,7 +181,7 @@ public class SecurityService implements InitializingBean { private static AuthInfo getAuthInfo(String auth) { AuthInfo authInfo = new AuthInfo(); if (auth != null) { - //内容为: Digest username="longruan", realm="jrp-auth@example.org", nonce="e9fbd885ad82a440b0879941e67447d3", uri="/", algorithm=MD5, response="b00144c208ac431dfb6deb6b9f0276cb", opaque="zpjmcNA626xb3kJF0v/kPg==", qop=auth, nc=00000002, cnonce="562de37a0569d379" + //内容为: Digest username="longruan", realm="jfp-auth@example.org", nonce="e9fbd885ad82a440b0879941e67447d3", uri="/", algorithm=MD5, response="b00144c208ac431dfb6deb6b9f0276cb", opaque="zpjmcNA626xb3kJF0v/kPg==", qop=auth, nc=00000002, cnonce="562de37a0569d379" for (String kv : auth.split(",")) { while (kv.startsWith(" ")){ kv = kv.substring(kv.indexOf(" ") + 1); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e01be72d4f0552b69598f71c2dc1dbf85212baa6..321ed9aa3f1b369cd5597f94745c73dcb4e800f0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,7 +3,7 @@ vertx: #服务配置管理端口 page-port: 10086 #服务配置管理接口路径 - page-path: /jrp + page-path: /jfp #正向代理端口 proxy-port: 1024 #认证用户名 @@ -21,7 +21,7 @@ logging: # security 日志 org.springframework.security: error # mybatis日志 - com.tony.jrp: debug + com.tony.jfp: debug charset: # 输出控制台编码 console: UTF-8 @@ -33,8 +33,8 @@ logging: # 输出文件名及路径,不配置则不输出文件 file: # 切记,该文件表示正在产出日志的日志文件。并不会打包,当文件大于max-file-size,会根据file-name-pattern格式打包 - # 名称为log/jrp.log文件夹会在项目根目录下,打包后会在启动包同目录下;名称为/log/jrp.log的文件夹会在项目所在磁盘的跟目录下 - name: log/jrp.log + # 名称为log/jfp.log文件夹会在项目根目录下,打包后会在启动包同目录下;名称为/log/jfp.log的文件夹会在项目所在磁盘的跟目录下 + name: log/jfp.log logback: rollingpolicy: clean-history-on-start: true @@ -45,7 +45,7 @@ logging: # 日志保存的天数 max-history: 30 # 打包文件格式,默认: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz,书写格式为:文件路径/文件名.%i.文件后缀,其中%i不可省去,否则无日志显示 - # 例如: 日期为:2021/11/5 ,则打包文件之后为: log/jrp.2021-11-05.0.gz,0表示日志的第一部分,后续就是,1,2,3... - # 如果是压缩包,里面会多一个名log/jrp.2021-11-05.0的日志文件 - # 如下面的例子,打包之后为: log/2021-11/jrp.2020-11-5.0.log,这是一个日志文件 - file-name-pattern: log/%d{yyyy-MM}/jrp.%d{yyyy-MM-dd}.%i.log \ No newline at end of file + # 例如: 日期为:2021/11/5 ,则打包文件之后为: log/jfp.2021-11-05.0.gz,0表示日志的第一部分,后续就是,1,2,3... + # 如果是压缩包,里面会多一个名log/jfp.2021-11-05.0的日志文件 + # 如下面的例子,打包之后为: log/2021-11/jfp.2020-11-5.0.log,这是一个日志文件 + file-name-pattern: log/%d{yyyy-MM}/jfp.%d{yyyy-MM-dd}.%i.log \ No newline at end of file