Skip to content

Commit f768df2

Browse files
committed
Add isStripAuthorizationOnRedirect() config option to control Authorization header on redirects
1 parent 73911eb commit f768df2

File tree

5 files changed

+153
-4
lines changed

5 files changed

+153
-4
lines changed

client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java

+7
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,13 @@ public interface AsyncHttpClientConfig {
375375

376376
int getIoThreadsCount();
377377

378+
/**
379+
* Indicates whether the Authorization header should be stripped during redirects to a different domain.
380+
*
381+
* @return true if the Authorization header should be stripped, false otherwise.
382+
*/
383+
boolean isStripAuthorizationOnRedirect();
384+
378385
enum ResponseBodyPartFactory {
379386

380387
EAGER {

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java

+16
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
127127
private final boolean keepEncodingHeader;
128128
private final ProxyServerSelector proxyServerSelector;
129129
private final boolean validateResponseHeaders;
130+
private final boolean stripAuthorizationOnRedirect;
130131

131132
// websockets
132133
private final boolean aggregateWebSocketFrameFragments;
@@ -219,6 +220,7 @@ private DefaultAsyncHttpClientConfig(// http
219220
boolean validateResponseHeaders,
220221
boolean aggregateWebSocketFrameFragments,
221222
boolean enablewebSocketCompression,
223+
boolean stripAuthorizationOnRedirect,
222224

223225
// timeouts
224226
Duration connectTimeout,
@@ -307,6 +309,7 @@ private DefaultAsyncHttpClientConfig(// http
307309
this.keepEncodingHeader = keepEncodingHeader;
308310
this.proxyServerSelector = proxyServerSelector;
309311
this.validateResponseHeaders = validateResponseHeaders;
312+
this.stripAuthorizationOnRedirect = stripAuthorizationOnRedirect;
310313

311314
// websocket
312315
this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments;
@@ -564,6 +567,11 @@ public boolean isValidateResponseHeaders() {
564567
return validateResponseHeaders;
565568
}
566569

570+
@Override
571+
public boolean isStripAuthorizationOnRedirect() {
572+
return stripAuthorizationOnRedirect;
573+
}
574+
567575
// ssl
568576
@Override
569577
public boolean isUseOpenSsl() {
@@ -800,6 +808,7 @@ public static class Builder {
800808
private boolean useProxySelector = defaultUseProxySelector();
801809
private boolean useProxyProperties = defaultUseProxyProperties();
802810
private boolean validateResponseHeaders = defaultValidateResponseHeaders();
811+
private boolean stripAuthorizationOnRedirect; // default value
803812

804813
// websocket
805814
private boolean aggregateWebSocketFrameFragments = defaultAggregateWebSocketFrameFragments();
@@ -891,6 +900,7 @@ public Builder(AsyncHttpClientConfig config) {
891900
keepEncodingHeader = config.isKeepEncodingHeader();
892901
proxyServerSelector = config.getProxyServerSelector();
893902
validateResponseHeaders = config.isValidateResponseHeaders();
903+
stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect();
894904

895905
// websocket
896906
aggregateWebSocketFrameFragments = config.isAggregateWebSocketFrameFragments();
@@ -1079,6 +1089,11 @@ public Builder setUseProxyProperties(boolean useProxyProperties) {
10791089
return this;
10801090
}
10811091

1092+
public Builder setStripAuthorizationOnRedirect(boolean value) {
1093+
stripAuthorizationOnRedirect = value;
1094+
return this;
1095+
}
1096+
10821097
// websocket
10831098
public Builder setAggregateWebSocketFrameFragments(boolean aggregateWebSocketFrameFragments) {
10841099
this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments;
@@ -1444,6 +1459,7 @@ public DefaultAsyncHttpClientConfig build() {
14441459
validateResponseHeaders,
14451460
aggregateWebSocketFrameFragments,
14461461
enablewebSocketCompression,
1462+
stripAuthorizationOnRedirect,
14471463
connectTimeout,
14481464
requestTimeout,
14491465
readTimeout,

client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import org.slf4j.LoggerFactory;
3636

3737
import java.util.HashSet;
38-
import java.util.List;
3938
import java.util.Set;
4039

4140
import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION;
@@ -73,11 +72,13 @@ public class Redirect30xInterceptor {
7372
private final AsyncHttpClientConfig config;
7473
private final NettyRequestSender requestSender;
7574
private final MaxRedirectException maxRedirectException;
75+
private final boolean stripAuthorizationOnRedirect;
7676

7777
Redirect30xInterceptor(ChannelManager channelManager, AsyncHttpClientConfig config, NettyRequestSender requestSender) {
7878
this.channelManager = channelManager;
7979
this.config = config;
8080
this.requestSender = requestSender;
81+
stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect(); // New flag
8182
maxRedirectException = unknownStackTrace(new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()),
8283
Redirect30xInterceptor.class, "exitAfterHandlingRedirect");
8384
}
@@ -127,7 +128,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
127128
}
128129
}
129130

130-
requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody));
131+
requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuthorizationOnRedirect));
131132

132133
// in case of a redirect from HTTP to HTTPS, future
133134
// attributes might change
@@ -180,7 +181,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
180181
return false;
181182
}
182183

183-
private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody) {
184+
private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) {
184185
HttpHeaders headers = request.getHeaders()
185186
.remove(HOST)
186187
.remove(CONTENT_LENGTH);
@@ -189,7 +190,7 @@ private static HttpHeaders propagatedHeaders(Request request, Realm realm, boole
189190
headers.remove(CONTENT_TYPE);
190191
}
191192

192-
if (realm != null && realm.getScheme() == AuthScheme.NTLM) {
193+
if (stripAuthorization || realm != null && realm.getScheme() == AuthScheme.NTLM) {
193194
headers.remove(AUTHORIZATION)
194195
.remove(PROXY_AUTHORIZATION);
195196
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.asynchttpclient;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.junit.jupiter.api.Assertions.assertFalse;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
class DefaultAsyncHttpClientConfigTest {
9+
@Test
10+
void testStripAuthorizationOnRedirect_DefaultIsFalse() {
11+
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build();
12+
assertFalse(config.isStripAuthorizationOnRedirect(), "Default should be false");
13+
}
14+
15+
@Test
16+
void testStripAuthorizationOnRedirect_SetTrue() {
17+
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
18+
.setStripAuthorizationOnRedirect(true)
19+
.build();
20+
assertTrue(config.isStripAuthorizationOnRedirect(), "Should be true when set");
21+
}
22+
23+
@Test
24+
void testStripAuthorizationOnRedirect_SetFalse() {
25+
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
26+
.setStripAuthorizationOnRedirect(false)
27+
.build();
28+
assertFalse(config.isStripAuthorizationOnRedirect(), "Should be false when set to false");
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package org.asynchttpclient;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import com.sun.net.httpserver.HttpHandler;
5+
import com.sun.net.httpserver.HttpServer;
6+
import org.junit.jupiter.api.AfterAll;
7+
import org.junit.jupiter.api.BeforeAll;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.net.InetSocketAddress;
11+
import java.util.concurrent.TimeUnit;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertNull;
15+
16+
public class StripAuthorizationOnRedirectHttpTest {
17+
private static HttpServer server;
18+
private static int port;
19+
private static volatile String lastAuthHeader;
20+
21+
@BeforeAll
22+
public static void startServer() throws Exception {
23+
server = HttpServer.create(new InetSocketAddress(0), 0);
24+
port = server.getAddress().getPort();
25+
server.createContext("/redirect", new RedirectHandler());
26+
server.createContext("/final", new FinalHandler());
27+
server.start();
28+
}
29+
30+
@AfterAll
31+
public static void stopServer() {
32+
server.stop(0);
33+
}
34+
35+
static class RedirectHandler implements HttpHandler {
36+
@Override
37+
public void handle(HttpExchange exchange) {
38+
String auth = exchange.getRequestHeaders().getFirst("Authorization");
39+
lastAuthHeader = auth;
40+
exchange.getResponseHeaders().add("Location", "http://localhost:" + port + "/final");
41+
try {
42+
exchange.sendResponseHeaders(302, -1);
43+
} catch (Exception ignored) {
44+
}
45+
exchange.close();
46+
}
47+
}
48+
49+
static class FinalHandler implements HttpHandler {
50+
@Override
51+
public void handle(HttpExchange exchange) {
52+
String auth = exchange.getRequestHeaders().getFirst("Authorization");
53+
lastAuthHeader = auth;
54+
try {
55+
exchange.sendResponseHeaders(200, 0);
56+
exchange.getResponseBody().close();
57+
} catch (Exception ignored) {
58+
}
59+
exchange.close();
60+
}
61+
}
62+
63+
@Test
64+
void testAuthHeaderPropagatedByDefault() throws Exception {
65+
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
66+
.setFollowRedirect(true)
67+
.build();
68+
try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) {
69+
lastAuthHeader = null;
70+
client.prepareGet("http://localhost:" + port + "/redirect")
71+
.setHeader("Authorization", "Bearer testtoken")
72+
.execute()
73+
.get(5, TimeUnit.SECONDS);
74+
// By default, Authorization header is propagated to /final
75+
assertEquals("Bearer testtoken", lastAuthHeader, "Authorization header should be present on redirect by default");
76+
}
77+
}
78+
79+
@Test
80+
void testAuthHeaderStrippedWhenEnabled() throws Exception {
81+
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
82+
.setFollowRedirect(true)
83+
.setStripAuthorizationOnRedirect(true)
84+
.build();
85+
try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) {
86+
lastAuthHeader = null;
87+
client.prepareGet("http://localhost:" + port + "/redirect")
88+
.setHeader("Authorization", "Bearer testtoken")
89+
.execute()
90+
.get(5, TimeUnit.SECONDS);
91+
// When enabled, Authorization header should be stripped on /final
92+
assertNull(lastAuthHeader, "Authorization header should be stripped on redirect when enabled");
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)