diff --git a/README.md b/README.md index a3bc951..2a5bbd4 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Proxyee is a JAVA written HTTP proxy server library that supports HTTP, HTTPS, W com.github.monkeywie proxyee - 1.3.2 + 1.3.3 ``` @@ -115,8 +115,31 @@ openssl req -sha256 -new -x509 -days 365 -key ca.key -out ca.crt \ Copy `ca.crt` and `ca_private.der` to the project src/resources/ after generation, or implement the HttpProxyCACertFactory interface to custom load the root certificate and private key. -## Pre-proxy support +## Authentication + +Currently only basic authentication are supported. + +- Basic +```java +// curl -i -x 127.0.0.1:9999 -U admin:123456 https://www.baidu.com +HttpProxyServerConfig config = new HttpProxyServerConfig(); + config.setAuthenticationProvider(new BasicHttpProxyAuthenticationProvider() { + @Override + protected boolean authenticate(String usr, String pwd) { + return "admin".equals(usr) && "123456".equals(pwd); + } + }); +new HttpProxyServer() + .serverConfig(config) + .start(9999); +``` + +- Custom + +Customize authentication by implementing the `HttpProxyAuthenticationProvider` interface. + +## Pre-proxy support Pre-proxy can be set,support http,socks4,socks5 protocol. diff --git a/README_zh-CN.md b/README_zh-CN.md index 5459bb8..cc33fdb 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -25,7 +25,7 @@ Proxyee 是一个 JAVA 编写的 HTTP 代理服务器类库,支持 HTTP、HTTP com.github.monkeywie proxyee - 1.3.2 + 1.3.3 ``` @@ -120,8 +120,31 @@ openssl req -sha256 -new -x509 -days 365 -key ca.key -out ca.crt \ 生成完之后把`ca.crt`和`ca_private.der`复制到项目的 src/resources/中,或者实现 HttpProxyCACertFactory 接口来自定义加载根证书和私钥 -## 前置代理支持 +## 身份验证 + +目前只支持基本身份验证方案。 + +- 基本验证 +```java +// curl -i -x 127.0.0.1:9999 -U admin:123456 https://www.baidu.com +HttpProxyServerConfig config = new HttpProxyServerConfig(); + config.setAuthenticationProvider(new BasicHttpProxyAuthenticationProvider() { + @Override + protected boolean authenticate(String usr, String pwd) { + return "admin".equals(usr) && "123456".equals(pwd); + } + }); +new HttpProxyServer() + .serverConfig(config) + .start(9999); +``` + +- 自定义验证 + +通过实现`HttpProxyAuthenticationProvider`接口来自定义验证。 + +## 前置代理支持 可设置前置代理,支持 http,socks4,socks5 协议 diff --git a/pom.xml b/pom.xml index 0bd587b..c3f0345 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.github.monkeywie proxyee - 1.3.2 + 1.3.3 diff --git a/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyClientHandle.java b/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyClientHandler.java similarity index 89% rename from src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyClientHandle.java rename to src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyClientHandler.java index 5fb8525..72313f9 100644 --- a/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyClientHandle.java +++ b/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyClientHandler.java @@ -9,11 +9,11 @@ import io.netty.handler.codec.http.HttpResponse; import io.netty.util.ReferenceCountUtil; -public class HttpProxyClientHandle extends ChannelInboundHandlerAdapter { +public class HttpProxyClientHandler extends ChannelInboundHandlerAdapter { private Channel clientChannel; - public HttpProxyClientHandle(Channel clientChannel) { + public HttpProxyClientHandler(Channel clientChannel) { this.clientChannel = clientChannel; } @@ -24,7 +24,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception ReferenceCountUtil.release(msg); return; } - HttpProxyInterceptPipeline interceptPipeline = ((HttpProxyServerHandle) clientChannel.pipeline() + HttpProxyInterceptPipeline interceptPipeline = ((HttpProxyServerHandler) clientChannel.pipeline() .get("serverHandle")).getInterceptPipeline(); if (msg instanceof HttpResponse) { interceptPipeline.afterResponse(clientChannel, ctx.channel(), (HttpResponse) msg); @@ -45,7 +45,7 @@ public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.channel().close(); clientChannel.close(); - HttpProxyExceptionHandle exceptionHandle = ((HttpProxyServerHandle) clientChannel.pipeline() + HttpProxyExceptionHandle exceptionHandle = ((HttpProxyServerHandler) clientChannel.pipeline() .get("serverHandle")).getExceptionHandle(); exceptionHandle.afterCatch(clientChannel, ctx.channel(), cause); } diff --git a/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyInitializer.java b/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyInitializer.java index 06db5a9..0bc65ca 100644 --- a/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyInitializer.java +++ b/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyInitializer.java @@ -29,11 +29,11 @@ protected void initChannel(Channel ch) throws Exception { } if (requestProto.getSsl()) { ch.pipeline().addLast( - ((HttpProxyServerHandle) clientChannel.pipeline().get("serverHandle")).getServerConfig() + ((HttpProxyServerHandler) clientChannel.pipeline().get("serverHandle")).getServerConfig() .getClientSslCtx() .newHandler(ch.alloc(), requestProto.getHost(), requestProto.getPort())); } ch.pipeline().addLast("httpCodec", new HttpClientCodec()); - ch.pipeline().addLast("proxyClientHandle", new HttpProxyClientHandle(clientChannel)); + ch.pipeline().addLast("proxyClientHandle", new HttpProxyClientHandler(clientChannel)); } } diff --git a/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyServerHandle.java b/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyServerHandler.java similarity index 89% rename from src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyServerHandle.java rename to src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyServerHandler.java index bfa8e7e..efe9ba7 100644 --- a/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyServerHandle.java +++ b/src/main/java/com/github/monkeywie/proxyee/handler/HttpProxyServerHandler.java @@ -10,6 +10,7 @@ import com.github.monkeywie.proxyee.proxy.ProxyHandleFactory; import com.github.monkeywie.proxyee.server.HttpProxyServer; import com.github.monkeywie.proxyee.server.HttpProxyServerConfig; +import com.github.monkeywie.proxyee.server.auth.HttpProxyAuthenticationProvider; import com.github.monkeywie.proxyee.util.ProtoUtil; import com.github.monkeywie.proxyee.util.ProtoUtil.RequestProto; import io.netty.bootstrap.Bootstrap; @@ -28,7 +29,7 @@ import java.util.LinkedList; import java.util.List; -public class HttpProxyServerHandle extends ChannelInboundHandlerAdapter { +public class HttpProxyServerHandler extends ChannelInboundHandlerAdapter { private ChannelFuture cf; private String host; @@ -56,7 +57,7 @@ public HttpProxyExceptionHandle getExceptionHandle() { return exceptionHandle; } - public HttpProxyServerHandle(HttpProxyServerConfig serverConfig, HttpProxyInterceptInitializer interceptInitializer, HttpTunnelIntercept tunnelIntercept, ProxyConfig proxyConfig, HttpProxyExceptionHandle exceptionHandle) { + public HttpProxyServerHandler(HttpProxyServerConfig serverConfig, HttpProxyInterceptInitializer interceptInitializer, HttpTunnelIntercept tunnelIntercept, ProxyConfig proxyConfig, HttpProxyExceptionHandle exceptionHandle) { this.serverConfig = serverConfig; this.proxyConfig = proxyConfig; this.interceptInitializer = interceptInitializer; @@ -75,6 +76,11 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throw ctx.channel().close(); return; } + if (!authenticate(ctx, request)) { + status = 2; + ctx.channel().close(); + return; + } status = 1; this.host = requestProto.getHost(); this.port = requestProto.getPort(); @@ -139,6 +145,19 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E exceptionHandle.beforeCatch(ctx.channel(), cause); } + private boolean authenticate(ChannelHandlerContext ctx, HttpRequest request) { + if (serverConfig.getAuthenticationProvider() != null) { + HttpProxyAuthenticationProvider authProvider = serverConfig.getAuthenticationProvider(); + if (!authProvider.authenticate(request.headers().get(HttpHeaderNames.PROXY_AUTHORIZATION))) { + HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpProxyServer.UNAUTHORIZED); + response.headers().set(HttpHeaderNames.PROXY_AUTHENTICATE, authProvider.authType() + " realm=\"" + authProvider.authRealm() + "\""); + ctx.writeAndFlush(response); + return false; + } + } + return true; + } + private void handleProxyData(Channel channel, Object msg, boolean isHttp) throws Exception { if (cf == null) { // connection异常 还有HttpContent进来,不转发 diff --git a/src/main/java/com/github/monkeywie/proxyee/handler/TunnelProxyInitializer.java b/src/main/java/com/github/monkeywie/proxyee/handler/TunnelProxyInitializer.java index 6344b7f..cf7ddfd 100644 --- a/src/main/java/com/github/monkeywie/proxyee/handler/TunnelProxyInitializer.java +++ b/src/main/java/com/github/monkeywie/proxyee/handler/TunnelProxyInitializer.java @@ -42,7 +42,7 @@ public void channelUnregistered(ChannelHandlerContext ctx0) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx0, Throwable cause) throws Exception { ctx0.channel().close(); clientChannel.close(); - HttpProxyExceptionHandle exceptionHandle = ((HttpProxyServerHandle) clientChannel.pipeline() + HttpProxyExceptionHandle exceptionHandle = ((HttpProxyServerHandler) clientChannel.pipeline() .get("serverHandle")).getExceptionHandle(); exceptionHandle.afterCatch(clientChannel, ctx0.channel(), cause); } diff --git a/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServer.java b/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServer.java index 287534d..4424ed0 100644 --- a/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServer.java +++ b/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServer.java @@ -3,7 +3,7 @@ import com.github.monkeywie.proxyee.crt.CertPool; import com.github.monkeywie.proxyee.crt.CertUtil; import com.github.monkeywie.proxyee.exception.HttpProxyExceptionHandle; -import com.github.monkeywie.proxyee.handler.HttpProxyServerHandle; +import com.github.monkeywie.proxyee.handler.HttpProxyServerHandler; import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptInitializer; import com.github.monkeywie.proxyee.intercept.HttpTunnelIntercept; import com.github.monkeywie.proxyee.proxy.ProxyConfig; @@ -30,6 +30,8 @@ public class HttpProxyServer { //http代理隧道握手成功 public final static HttpResponseStatus SUCCESS = new HttpResponseStatus(200, "Connection established"); + public final static HttpResponseStatus UNAUTHORIZED = new HttpResponseStatus(407, + "Unauthorized"); private HttpProxyCACertFactory caCertFactory; private HttpProxyServerConfig serverConfig; @@ -137,7 +139,7 @@ public void start(String ip, int port) { protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast("httpCodec", new HttpServerCodec()); ch.pipeline().addLast("serverHandle", - new HttpProxyServerHandle(serverConfig, proxyInterceptInitializer, tunnelIntercept, proxyConfig, + new HttpProxyServerHandler(serverConfig, proxyInterceptInitializer, tunnelIntercept, proxyConfig, httpProxyExceptionHandle)); } }); diff --git a/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServerConfig.java b/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServerConfig.java index 8f10905..abc50f9 100644 --- a/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServerConfig.java +++ b/src/main/java/com/github/monkeywie/proxyee/server/HttpProxyServerConfig.java @@ -1,5 +1,6 @@ package com.github.monkeywie.proxyee.server; +import com.github.monkeywie.proxyee.server.auth.HttpProxyAuthenticationProvider; import io.netty.channel.EventLoopGroup; import io.netty.handler.ssl.SslContext; import io.netty.resolver.AddressResolverGroup; @@ -23,6 +24,7 @@ public class HttpProxyServerConfig { private int workerGroupThreads; private int proxyGroupThreads; private boolean handleSsl; + private HttpProxyAuthenticationProvider authenticationProvider; private final AddressResolverGroup resolver; public HttpProxyServerConfig() { @@ -129,6 +131,14 @@ public void setProxyGroupThreads(int proxyGroupThreads) { this.proxyGroupThreads = proxyGroupThreads; } + public HttpProxyAuthenticationProvider getAuthenticationProvider() { + return authenticationProvider; + } + + public void setAuthenticationProvider(final HttpProxyAuthenticationProvider authenticationProvider) { + this.authenticationProvider = authenticationProvider; + } + public AddressResolverGroup resolver() { return resolver; } diff --git a/src/main/java/com/github/monkeywie/proxyee/server/auth/BasicHttpProxyAuthenticationProvider.java b/src/main/java/com/github/monkeywie/proxyee/server/auth/BasicHttpProxyAuthenticationProvider.java new file mode 100644 index 0000000..39fbea8 --- /dev/null +++ b/src/main/java/com/github/monkeywie/proxyee/server/auth/BasicHttpProxyAuthenticationProvider.java @@ -0,0 +1,40 @@ +package com.github.monkeywie.proxyee.server.auth; + +import java.util.Base64; + +/** + * @Author LiWei + * @Description + * @Date 2021/1/15 14:12 + */ +public abstract class BasicHttpProxyAuthenticationProvider implements HttpProxyAuthenticationProvider { + + public static final String AUTH_TYPE_BASIC = "Basic"; + public static final String AUTH_REALM_BASIC = "Access to the staging site"; + + public String authType() { + return AUTH_TYPE_BASIC; + } + + public String authRealm() { + return AUTH_REALM_BASIC; + } + + protected abstract boolean authenticate(String usr, String pwd); + + public boolean authenticate(String authorization) { + String usr = ""; + String pwd = ""; + if (authorization != null && authorization.length() > 0) { + String token = authorization.substring(AUTH_TYPE_BASIC.length() + 1); + String decode = new String(Base64.getDecoder().decode(token)); + String[] arr = decode.split(":"); + usr = arr[0]; + if (arr.length >= 2) { + pwd = arr[1]; + } + } + return authenticate(usr, pwd); + } + +} diff --git a/src/main/java/com/github/monkeywie/proxyee/server/auth/HttpProxyAuthenticationProvider.java b/src/main/java/com/github/monkeywie/proxyee/server/auth/HttpProxyAuthenticationProvider.java new file mode 100644 index 0000000..fe335f2 --- /dev/null +++ b/src/main/java/com/github/monkeywie/proxyee/server/auth/HttpProxyAuthenticationProvider.java @@ -0,0 +1,14 @@ +package com.github.monkeywie.proxyee.server.auth; + +/** + * @Author LiWei + * @Description + * @Date 2021/1/15 14:12 + */ +public interface HttpProxyAuthenticationProvider { + String authType(); + + String authRealm(); + + boolean authenticate(String authorization); +} diff --git a/src/test/java/com/github/monkeywie/proxyee/AuthHttpProxyServer.java b/src/test/java/com/github/monkeywie/proxyee/AuthHttpProxyServer.java new file mode 100644 index 0000000..28b85a4 --- /dev/null +++ b/src/test/java/com/github/monkeywie/proxyee/AuthHttpProxyServer.java @@ -0,0 +1,22 @@ +package com.github.monkeywie.proxyee; + +import com.github.monkeywie.proxyee.server.HttpProxyServer; +import com.github.monkeywie.proxyee.server.HttpProxyServerConfig; +import com.github.monkeywie.proxyee.server.auth.BasicHttpProxyAuthenticationProvider; + +public class AuthHttpProxyServer { + + // curl -i -x 127.0.0.1:9999 -U admin:123456 https://www.baidu.com + public static void main(String[] args) throws Exception { + HttpProxyServerConfig config = new HttpProxyServerConfig(); + config.setAuthenticationProvider(new BasicHttpProxyAuthenticationProvider() { + @Override + protected boolean authenticate(String usr, String pwd) { + return "admin".equals(usr) && "123456".equals(pwd); + } + }); + new HttpProxyServer() + .serverConfig(config) + .start(9999); + } +}