ASF Bugzilla – Attachment 35984 Details for
Bug 62312
Add Proxy Authentication support to websocket client
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
Git diffs for proxy authentication support
websocket-proxy-auth.diff (text/plain), 24.05 KB, created by
Joe Mokos
on 2018-06-22 17:12:50 UTC
(
hide
)
Description:
Git diffs for proxy authentication support
Filename:
MIME Type:
Creator:
Joe Mokos
Created:
2018-06-22 17:12:50 UTC
Size:
24.05 KB
patch
obsolete
>diff --git a/java/org/apache/tomcat/websocket/Authenticator.java b/java/org/apache/tomcat/websocket/Authenticator.java >index bc14d57..c89bffb 100644 >--- a/java/org/apache/tomcat/websocket/Authenticator.java >+++ b/java/org/apache/tomcat/websocket/Authenticator.java >@@ -68,4 +68,24 @@ public abstract class Authenticator { > > } > >+ /** >+ * Static utility method to validate the users realm. >+ * @param authenticateRealm the realm return in the authenticate header >+ * @param userRealm the realm of the user >+ * @return nada >+ * @throws AuthenticationException >+ */ >+ protected static void validateRealm(String authenticateRealm, String userRealm) throws AuthenticationException { >+ >+ if (authenticateRealm != null && userRealm != null) { >+ String ar = authenticateRealm.trim(); >+ String ur = userRealm.trim(); >+ if (!ar.isEmpty() && !ur.isEmpty()) { >+ if (!ar.equals(ur)) { >+ throw new AuthenticationException( >+ "Failed to perform Digest authentication due to realm mismatch"); >+ } >+ } >+ } >+ } > } >diff --git a/java/org/apache/tomcat/websocket/AuthenticatorFactory.java b/java/org/apache/tomcat/websocket/AuthenticatorFactory.java >index 0d28da4..1f784cc 100644 >--- a/java/org/apache/tomcat/websocket/AuthenticatorFactory.java >+++ b/java/org/apache/tomcat/websocket/AuthenticatorFactory.java >@@ -24,6 +24,8 @@ import java.util.ServiceLoader; > * the scheme that the server uses. > */ > public class AuthenticatorFactory { >+ >+ public enum Type {WWW, PROXY} > > /** > * Return a new authenticator instance. >@@ -32,15 +34,25 @@ public class AuthenticatorFactory { > */ > public static Authenticator getAuthenticator(String authScheme) { > >+ return getAuthenticator(Type.WWW, authScheme); >+ } >+ >+ /** >+ * Return a new authenticator instance. >+ * @param authScheme The scheme used >+ * @return the authenticator >+ */ >+ public static Authenticator getAuthenticator(Type type, String authScheme) { >+ > Authenticator auth = null; > switch (authScheme.toLowerCase()) { > > case BasicAuthenticator.schemeName: >- auth = new BasicAuthenticator(); >+ auth = (type == Type.PROXY) ? new ProxyBasicAuthenticator() : new BasicAuthenticator(); > break; > > case DigestAuthenticator.schemeName: >- auth = new DigestAuthenticator(); >+ auth = (type == Type.PROXY) ? new ProxyDigestAuthenticator() : new DigestAuthenticator(); > break; > > default: >diff --git a/java/org/apache/tomcat/websocket/BasicAuthenticator.java b/java/org/apache/tomcat/websocket/BasicAuthenticator.java >index 49c58b0..c3b79c4 100644 >--- a/java/org/apache/tomcat/websocket/BasicAuthenticator.java >+++ b/java/org/apache/tomcat/websocket/BasicAuthenticator.java >@@ -35,6 +35,14 @@ public class BasicAuthenticator extends Authenticator { > > String userName = (String) userProperties.get(Constants.WS_AUTHENTICATION_USER_NAME); > String password = (String) userProperties.get(Constants.WS_AUTHENTICATION_PASSWORD); >+ String realm = (String) userProperties.get(Constants.WS_AUTHENTICATION_REALM); >+ >+ return getAuthorization(userName, password, realm, requestUri, WWWAuthenticate, userProperties); >+ } >+ >+// @Override >+ protected String getAuthorization(String userName, String password, String userRealm, String requestUri, String WWWAuthenticate, >+ Map<String, Object> userProperties) throws AuthenticationException { > > if (userName == null || password == null) { > throw new AuthenticationException( >@@ -43,6 +51,9 @@ public class BasicAuthenticator extends Authenticator { > > Map<String, String> wwwAuthenticate = parseWWWAuthenticateHeader(WWWAuthenticate); > >+ String realm = wwwAuthenticate.get("realm"); >+ validateRealm(realm, userRealm); >+ > String userPass = userName + ":" + password; > Charset charset; > >diff --git a/java/org/apache/tomcat/websocket/Constants.java b/java/org/apache/tomcat/websocket/Constants.java >index eeee8cc..72a2163 100644 >--- a/java/org/apache/tomcat/websocket/Constants.java >+++ b/java/org/apache/tomcat/websocket/Constants.java >@@ -88,6 +88,8 @@ public class Constants { > public static final String CONNECTION_HEADER_NAME = "Connection"; > public static final String CONNECTION_HEADER_VALUE = "upgrade"; > public static final String LOCATION_HEADER_NAME = "Location"; >+ public static final String PROXY_AUTHORIZATION_HEADER_NAME = "Proxy-Authorization"; >+ public static final String PROXY_AUTHENTICATE_HEADER_NAME = "Proxy-Authenticate"; > public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; > public static final String WWW_AUTHENTICATE_HEADER_NAME = "WWW-Authenticate"; > public static final String WS_VERSION_HEADER_NAME = "Sec-WebSocket-Version"; >@@ -121,6 +123,10 @@ public class Constants { > > public static final String WS_AUTHENTICATION_USER_NAME = "org.apache.tomcat.websocket.WS_AUTHENTICATION_USER_NAME"; > public static final String WS_AUTHENTICATION_PASSWORD = "org.apache.tomcat.websocket.WS_AUTHENTICATION_PASSWORD"; >+ public static final String WS_AUTHENTICATION_REALM = "org.apache.tomcat.websocket.WS_AUTHENTICATION_REALM"; >+ public static final String WS_PROXY_AUTHENTICATION_USER_NAME = "org.apache.tomcat.websocket.WS_PROXY_AUTHENTICATION_USER_NAME"; >+ public static final String WS_PROXY_AUTHENTICATION_PASSWORD = "org.apache.tomcat.websocket.WS_PROXY_AUTHENTICATION_PASSWORD"; >+ public static final String WS_PROXY_AUTHENTICATION_REALM = "org.apache.tomcat.websocket.WS_PROXY_AUTHENTICATION_REALM"; > > /* Configuration for extensions > * Note: These options are primarily present to enable this implementation >diff --git a/java/org/apache/tomcat/websocket/DigestAuthenticator.java b/java/org/apache/tomcat/websocket/DigestAuthenticator.java >index 021489f..8655142 100644 >--- a/java/org/apache/tomcat/websocket/DigestAuthenticator.java >+++ b/java/org/apache/tomcat/websocket/DigestAuthenticator.java >@@ -40,6 +40,13 @@ public class DigestAuthenticator extends Authenticator { > > String userName = (String) userProperties.get(Constants.WS_AUTHENTICATION_USER_NAME); > String password = (String) userProperties.get(Constants.WS_AUTHENTICATION_PASSWORD); >+ String realm = (String) userProperties.get(Constants.WS_AUTHENTICATION_REALM); >+ >+ return getAuthorization(userName, password, realm, "GET", requestUri, WWWAuthenticate, userProperties); >+ } >+ >+ protected String getAuthorization(String userName, String password, String userRealm, String method, >+ String requestUri, String WWWAuthenticate, Map<String, Object> userProperties) throws AuthenticationException { > > if (userName == null || password == null) { > throw new AuthenticationException( >@@ -49,6 +56,8 @@ public class DigestAuthenticator extends Authenticator { > Map<String, String> wwwAuthenticate = parseWWWAuthenticateHeader(WWWAuthenticate); > > String realm = wwwAuthenticate.get("realm"); >+ validateRealm(realm, userRealm); >+ > String nonce = wwwAuthenticate.get("nonce"); > String messageQop = wwwAuthenticate.get("qop"); > String algorithm = wwwAuthenticate.get("algorithm") == null ? "MD5" >@@ -73,7 +82,7 @@ public class DigestAuthenticator extends Authenticator { > challenge.append("uri=\"" + requestUri + "\","); > > try { >- challenge.append("response=\"" + calculateRequestDigest(requestUri, userName, password, >+ challenge.append("response=\"" + calculateRequestDigest(method, requestUri, userName, password, > realm, nonce, messageQop, algorithm) + "\","); > } > >@@ -94,8 +103,8 @@ public class DigestAuthenticator extends Authenticator { > return challenge.toString(); > > } >- >- private String calculateRequestDigest(String requestUri, String userName, String password, >+ >+ private String calculateRequestDigest(String method, String requestUri, String userName, String password, > String realm, String nonce, String qop, String algorithm) > throws NoSuchAlgorithmException { > >@@ -113,7 +122,7 @@ public class DigestAuthenticator extends Authenticator { > * digest-uri-value ":" H(entity-body) since we do not have an entity-body, A2 = > * Method ":" digest-uri-value for auth and auth_int > */ >- String A2 = "GET:" + requestUri; >+ String A2 = method + ":" + requestUri; > > preDigest.append(encodeMD5(A1)); > preDigest.append(":"); >diff --git a/java/org/apache/tomcat/websocket/LocalStrings.properties b/java/org/apache/tomcat/websocket/LocalStrings.properties >index b5ccb85..cb64c8b 100644 >--- a/java/org/apache/tomcat/websocket/LocalStrings.properties >+++ b/java/org/apache/tomcat/websocket/LocalStrings.properties >@@ -139,6 +139,9 @@ wsWebSocketContainer.proxyConnectFail=Failed to connect to the configured Proxy > wsWebSocketContainer.sslEngineFail=Unable to create SSLEngine to support SSL/TLS connections > wsWebSocketContainer.missingLocationHeader=Failed to handle HTTP response code [{0}]. Missing Location header in response > wsWebSocketContainer.redirectThreshold=Cyclic Location header [{0}] detected / reached max number of redirects [{1}] of max [{2}] >-wsWebSocketContainer.unsupportedAuthScheme=Failed to handle HTTP response code [{0}]. Unsupported Authentication scheme [{1}] returned in response >-wsWebSocketContainer.failedAuthentication=Failed to handle HTTP response code [{0}]. Authentication header was not accepted by server. >-wsWebSocketContainer.missingWWWAuthenticateHeader=Failed to handle HTTP response code [{0}]. Missing WWW-Authenticate header in response >\ No newline at end of file >+wsWebSocketContainer.noSupportedAuthScheme=Failed to handle HTTP response code [{0}]. No supported Authentication scheme returned in response [{1}] >+wsWebSocketContainer.failedAuthentication=Failed to handle HTTP response code [{0}]. Authorization header was not accepted by server. >+wsWebSocketContainer.missingWWWAuthenticateHeader=Failed to handle HTTP response code [{0}]. Missing WWW-Authenticate header in response >+wsWebSocketContainer.noSupportedProxyAuthScheme=Failed to handle HTTP response code [{0}]. No supported Proxy Authentication scheme returned in response [{1}] >+wsWebSocketContainer.failedProxyAuthentication=Failed to handle HTTP response code [{0}]. Proxy-Authorization header was not accepted by server. >+wsWebSocketContainer.missingProxyAuthenticateHeader=Failed to handle HTTP response code [{0}]. Missing Proxy-Authenticate header in response >diff --git a/java/org/apache/tomcat/websocket/ProxyBasicAuthenticator.java b/java/org/apache/tomcat/websocket/ProxyBasicAuthenticator.java >new file mode 100644 >index 0000000..0c564b6 >--- /dev/null >+++ b/java/org/apache/tomcat/websocket/ProxyBasicAuthenticator.java >@@ -0,0 +1,34 @@ >+/* >+ * Licensed to the Apache Software Foundation (ASF) under one or more >+ * contributor license agreements. See the NOTICE file distributed with >+ * this work for additional information regarding copyright ownership. >+ * The ASF licenses this file to You under the Apache License, Version 2.0 >+ * (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * >+ * http://www.apache.org/licenses/LICENSE-2.0 >+ * >+ * Unless required by applicable law or agreed to in writing, software >+ * distributed under the License is distributed on an "AS IS" BASIS, >+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >+ * See the License for the specific language governing permissions and >+ * limitations under the License. >+ */ >+package org.apache.tomcat.websocket; >+ >+import java.util.Map; >+ >+public class ProxyBasicAuthenticator extends BasicAuthenticator { >+ >+ @Override >+ public String getAuthorization(String requestUri, String WWWAuthenticate, >+ Map<String, Object> userProperties) throws AuthenticationException { >+ >+ String userName = (String) userProperties.get(Constants.WS_PROXY_AUTHENTICATION_USER_NAME); >+ String password = (String) userProperties.get(Constants.WS_PROXY_AUTHENTICATION_PASSWORD); >+ String realm = (String) userProperties.get(Constants.WS_PROXY_AUTHENTICATION_REALM); >+ >+ return getAuthorization(userName, password, realm, requestUri, WWWAuthenticate, userProperties); >+ } >+ >+} >diff --git a/java/org/apache/tomcat/websocket/ProxyDigestAuthenticator.java b/java/org/apache/tomcat/websocket/ProxyDigestAuthenticator.java >new file mode 100644 >index 0000000..2f34136 >--- /dev/null >+++ b/java/org/apache/tomcat/websocket/ProxyDigestAuthenticator.java >@@ -0,0 +1,37 @@ >+/* >+ * Licensed to the Apache Software Foundation (ASF) under one or more >+ * contributor license agreements. See the NOTICE file distributed with >+ * this work for additional information regarding copyright ownership. >+ * The ASF licenses this file to You under the Apache License, Version 2.0 >+ * (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * >+ * http://www.apache.org/licenses/LICENSE-2.0 >+ * >+ * Unless required by applicable law or agreed to in writing, software >+ * distributed under the License is distributed on an "AS IS" BASIS, >+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >+ * See the License for the specific language governing permissions and >+ * limitations under the License. >+ */ >+package org.apache.tomcat.websocket; >+ >+import java.util.Map; >+ >+public class ProxyDigestAuthenticator extends DigestAuthenticator { >+ >+ /* (non-Javadoc) >+ * @see org.apache.tomcat.websocket.Authenticator#getAuthorization(java.lang.String, java.lang.String, java.util.Map) >+ */ >+ @Override >+ public String getAuthorization(String requestUri, String WWWAuthenticate, Map<String, Object> userProperties) >+ throws AuthenticationException { >+ >+ String userName = (String) userProperties.get(Constants.WS_PROXY_AUTHENTICATION_USER_NAME); >+ String password = (String) userProperties.get(Constants.WS_PROXY_AUTHENTICATION_PASSWORD); >+ String realm = (String) userProperties.get(Constants.WS_PROXY_AUTHENTICATION_REALM); >+ >+ return getAuthorization(userName, password, realm, "CONNECT", requestUri, WWWAuthenticate, userProperties); >+ } >+ >+} >diff --git a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java >index d93393e..64f9541 100644 >--- a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java >+++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java >@@ -259,7 +259,7 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce > if (sa == null) { > sa = new InetSocketAddress(host, port); > } else { >- proxyConnect = createProxyRequest(host, port); >+ proxyConnect = createProxyRequest(host, port, clientEndpointConfiguration); > } > > // Create the initial HTTP request to open the WebSocket connection >@@ -310,13 +310,65 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce > channel = new AsyncChannelWrapperNonSecure(socketChannel); > writeRequest(channel, proxyConnect, timeout); > HttpResponse httpResponse = processResponse(response, channel, timeout); >- if (httpResponse.getStatus() != 200) { >+ >+ if (httpResponse.getStatus() == 407) { >+ >+ if (userProperties.get(Constants.PROXY_AUTHORIZATION_HEADER_NAME) != null) { >+ throw new DeploymentException(sm.getString( >+ "wsWebSocketContainer.failedProxyAuthentication", >+ Integer.valueOf(httpResponse.status))); >+ } >+ >+ List<String> proxyAuthenticateHeaders = httpResponse.getHandshakeResponse() >+ .getHeaders().get(Constants.PROXY_AUTHENTICATE_HEADER_NAME); >+ >+ if (proxyAuthenticateHeaders == null || proxyAuthenticateHeaders.isEmpty()) { >+ throw new DeploymentException(sm.getString( >+ "wsWebSocketContainer.missingProxyAuthenticateHeader", >+ Integer.toString(httpResponse.status))); >+ } >+ >+ String authenticateHeader = null; >+ StringBuffer sb = new StringBuffer(); >+ Authenticator auth = null; >+ >+ for (String ah : proxyAuthenticateHeaders) { >+ if (ah != null) { >+ String authScheme = ah.trim().split("\\s+", 2)[0]; >+ auth = AuthenticatorFactory.getAuthenticator(AuthenticatorFactory.Type.PROXY, authScheme); >+ if (auth != null) { >+ authenticateHeader = ah; >+ break; >+ } >+ if (sb.length() > 0) { >+ sb.append(','); >+ } >+ sb.append(authScheme); >+ } >+ } >+ >+ if (auth == null) { >+ throw new DeploymentException( >+ sm.getString( >+ "wsWebSocketContainer.noSupportedProxyAuthScheme", >+ Integer.toString(httpResponse.status), sb.toString())); >+ } >+ >+ String requestUri = new String(proxyConnect.array(), StandardCharsets.ISO_8859_1) >+ .split("\\s", 3)[1]; >+ >+ userProperties.put(Constants.PROXY_AUTHORIZATION_HEADER_NAME, auth.getAuthorization( >+ requestUri, authenticateHeader, userProperties)); >+ >+ return connectToServerRecursive(endpoint, clientEndpointConfiguration, path, redirectSet); >+ } >+ else if (httpResponse.getStatus() != 200) { > throw new DeploymentException(sm.getString( > "wsWebSocketContainer.proxyConnectFail", selectedProxy, > Integer.toString(httpResponse.getStatus()))); > } > } catch (TimeoutException | InterruptedException | ExecutionException | >- EOFException e) { >+ EOFException | AuthenticationException e) { > if (channel != null) { > channel.close(); > } >@@ -405,27 +457,43 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce > List<String> wwwAuthenticateHeaders = httpResponse.getHandshakeResponse() > .getHeaders().get(Constants.WWW_AUTHENTICATE_HEADER_NAME); > >- if (wwwAuthenticateHeaders == null || wwwAuthenticateHeaders.isEmpty() || >- wwwAuthenticateHeaders.get(0) == null || wwwAuthenticateHeaders.get(0).isEmpty()) { >+ if (wwwAuthenticateHeaders == null || wwwAuthenticateHeaders.isEmpty()) { > throw new DeploymentException(sm.getString( > "wsWebSocketContainer.missingWWWAuthenticateHeader", > Integer.toString(httpResponse.status))); > } > >- String authScheme = wwwAuthenticateHeaders.get(0).split("\\s+", 2)[0]; >- String requestUri = new String(request.array(), StandardCharsets.ISO_8859_1) >- .split("\\s", 3)[1]; >- >- Authenticator auth = AuthenticatorFactory.getAuthenticator(authScheme); >+ String authenticateHeader = null; >+ StringBuffer sb = new StringBuffer(); >+ Authenticator auth = null; >+ >+ for (String ah : wwwAuthenticateHeaders) { >+ if (ah != null) { >+ String authScheme = ah.trim().split("\\s+", 2)[0]; >+ auth = AuthenticatorFactory.getAuthenticator(AuthenticatorFactory.Type.WWW, authScheme); >+ if (auth != null) { >+ authenticateHeader = ah; >+ break; >+ } >+ if (sb.length() > 0) { >+ sb.append(','); >+ } >+ sb.append(authScheme); >+ } >+ } > > if (auth == null) { > throw new DeploymentException( >- sm.getString("wsWebSocketContainer.unsupportedAuthScheme", >- Integer.valueOf(httpResponse.status), authScheme)); >+ sm.getString( >+ "wsWebSocketContainer.noSupportedWWWAuthScheme", >+ Integer.toString(httpResponse.status), sb.toString())); > } > >+ String requestUri = new String(request.array(), StandardCharsets.ISO_8859_1) >+ .split("\\s", 3)[1]; >+ > userProperties.put(Constants.AUTHORIZATION_HEADER_NAME, auth.getAuthorization( >- requestUri, wwwAuthenticateHeaders.get(0), userProperties)); >+ requestUri, authenticateHeader, userProperties)); > > return connectToServerRecursive(endpoint, clientEndpointConfiguration, path, redirectSet); > >@@ -560,7 +628,8 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce > } > > >- private static ByteBuffer createProxyRequest(String host, int port) { >+ private static ByteBuffer createProxyRequest(String host, int port, >+ ClientEndpointConfig clientEndpointConfiguration) { > StringBuilder request = new StringBuilder(); > request.append("CONNECT "); > request.append(host); >@@ -572,6 +641,15 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce > request.append(':'); > request.append(port); > >+ Map<String, Object> userProperties = clientEndpointConfiguration.getUserProperties(); >+ String proxyAuthString = (String) userProperties.get(Constants.PROXY_AUTHORIZATION_HEADER_NAME); >+ if (proxyAuthString != null) { >+ request.append("\r\n"); >+ request.append(Constants.PROXY_AUTHORIZATION_HEADER_NAME); >+ request.append(":"); >+ request.append(proxyAuthString); >+ } >+ > request.append("\r\n\r\n"); > > byte[] bytes = request.toString().getBytes(StandardCharsets.ISO_8859_1); >diff --git a/webapps/docs/web-socket-howto.xml b/webapps/docs/web-socket-howto.xml >index 69b8baf..9bb8591 100644 >--- a/webapps/docs/web-socket-howto.xml >+++ b/webapps/docs/web-socket-howto.xml >@@ -133,6 +133,22 @@ > <ocde>org.apache.tomcat.websocket.MAX_REDIRECTIONS</ocde>. The default value > is 20. Redirection support can be disabled by configuring a value of zero.</p> > >+<p>When using the WebSocket client to connect to a server endpoint through a proxy >+ that requires authentication the proxy user name, password and realm are set via the >+ <code>userProperties</code> of the provided <code>javax.websocket.ClientEndpointConfig</code>. >+ The following user properties must be set:</p> >+ <ul> >+ <li><code>org.apache.tomcat.websocket.WS_PROXY_AUTHENTICATION_USER_NAME</code></li> >+ <li><code>org.apache.tomcat.websocket.WS_PROXY_AUTHENTICATION_PASSWORD</code></li> >+ </ul> >+ The following user property is optional:</p> >+ <ul> >+ <li><code>org.apache.tomcat.websocket.WS_PROXY_AUTHENTICATION_REALM</code></li> >+ </ul> >+ <p>These properties must be set to valid credentials as recognized by the proxy. If >+ these credentials are invalid or not supplied the websocket upgrade request will be >+ rejected with a HTTP 407 status.</p> >+ > </section> > > </body>
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Actions:
View
Attachments on
bug 62312
:
35880
|
35887
| 35984