Skip to content

Commit e853e6a

Browse files
committed
No X-Forwarded-For support fix #1634
1 parent c7f9796 commit e853e6a

17 files changed

Lines changed: 536 additions & 49 deletions

File tree

docs/asciidoc/handlers.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ include::handlers/csrf.adoc[]
1010

1111
include::handlers/head.adoc[]
1212

13+
include::handlers/proxy-peer-address.adoc[]
14+
1315
include::handlers/rate-limit.adoc[]
1416

1517
include::handlers/ssl.adoc[]

docs/asciidoc/handlers/access-log.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,8 @@ Extra request or response headers can be appended at the end using the available
5454
- javadoc:AccessLogHandler[requestHeader, java.lang.String...]
5555
- javadoc:AccessLogHandler[responseHeader, java.lang.String...]
5656

57+
[TIP]
58+
====
59+
If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
60+
please consider to add the <<handlers-proxypeeraddresshandler, ProxyPeerAddressHandler>> to your pipeline.
61+
====
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
=== ProxyPeerAddressHandler
2+
3+
The javadoc:ProxyPeerAddressHandler[] handles `X-Forwarded-*` headers by updating the values on the
4+
current context to match what was sent in the header(s).
5+
6+
This should only be installed behind a reverse proxy that has been configured to send the
7+
`X-Forwarded-*` header, otherwise a remote user can spoof their address by sending a header with
8+
bogus values.
9+
10+
.Usage
11+
[source, java, role = "primary"]
12+
----
13+
import io.jooby.Jooby;
14+
import io.jooby.ProxyPeerAddressHandler;
15+
...
16+
{
17+
18+
decorator(new ProxyPeerAddressHandler()); <1>
19+
20+
get("/", ctx -> {
21+
String remoteAddress = ctx.getRemoteAddress(); <2>
22+
String scheme = ctx.getScheme(); <3>
23+
String host = ctx.getHost(); <4>
24+
int port = ctx.getPort(); <5>
25+
...
26+
});
27+
}
28+
----
29+
30+
.Kotlin
31+
[source, kotlin, role = "secondary"]
32+
----
33+
import io.jooby.Jooby
34+
import io.jooby.ProxyPeerAddressHandler
35+
...
36+
{
37+
decorator(ProxyPeerAddressHandler()) <1>
38+
39+
get("/") {
40+
val remoteAddress = ctx.remoteAddress <2>
41+
val scheme = ctx.scheme <3>
42+
val host = ctx.host <4>
43+
val port = ctx.port <5>
44+
...
45+
}
46+
}
47+
----
48+
49+
<1> Install ProxyPeerAddressHandler
50+
<2> Set `remote address` from `X-Forwarded-For`
51+
<3> Set `scheme` from `X-Forwarded-Proto`
52+
<4> Set `host` from `X-Forwarded-Host`
53+
<5> Set `port` from `X-Forwarded-Host` or `X-Forwarded-Port`
54+

docs/asciidoc/handlers/ssl.adoc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import io.jooby.SSLHandler;
1212
1313
setServerOptions(new ServerOptions().setSecurePort(8443));
1414
15-
before(new SSLHandler(false)); <1>
15+
before(new SSLHandler()); <1>
1616
1717
get("/", ctx -> {
1818
return ctx.getScheme();
@@ -31,7 +31,7 @@ import io.jooby.SSHandler
3131
securePort = 8443
3232
}
3333
34-
before(SSLHandler(false)) <1>
34+
before(SSLHandler()) <1>
3535
3636
get("/") {
3737
...
@@ -42,7 +42,8 @@ import io.jooby.SSHandler
4242
<1> Install SSLHandler
4343

4444
The SSL Handler recreates the HTTPs URL version using the `Host` header, if you are behind a proxy
45-
you will need to use the `X-Forwarded-Host` header. To do that just pass `true` to the SSLHandler.
45+
you will need to use the `X-Forwarded-Host` header. To do that install the
46+
<<handlers-proxypeeraddresshandler, ProxyPeerAddressHandler>> into your pipeline.
4647

4748
Optionally, you can specify the host to use:
4849

@@ -84,3 +85,9 @@ import io.jooby.SSHandler
8485
----
8586

8687
For more information about SSL, please check the <<server-ssl, configure SSL>> section.
88+
89+
[TIP]
90+
====
91+
If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
92+
please consider to add the link:proxy-peer-address.adoc[] to your pipeline.
93+
====

jooby/src/main/java/io/jooby/AccessLogHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
* <a href="https://en.wikipedia.org/wiki/Common_Log_Format">NCSA format</a> (a.k.a common log
2929
* format).
3030
* </p>
31+
*
32+
* If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
33+
* please consider to add {@link ProxyPeerAddressHandler} to your pipeline.
34+
*
3135
* <h2>usage</h2>
3236
*
3337
* <pre>{@code

jooby/src/main/java/io/jooby/Context.java

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -452,13 +452,19 @@ public interface Context extends Registry {
452452
/**
453453
* Recreates full/entire request url using the <code>Host</code> header.
454454
*
455+
* If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
456+
* please consider to add {@link ProxyPeerAddressHandler} to your pipeline.
457+
*
455458
* @return Full/entire request url using the <code>Host</code> header.
456459
*/
457460
@Nonnull String getRequestURL();
458461

459462
/**
460463
* Recreates full/entire request url using the <code>Host</code> header.
461464
*
465+
* If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
466+
* please consider to add {@link ProxyPeerAddressHandler} to your pipeline.
467+
*
462468
* @param path Path to use.
463469
* @return Full/entire request url using the <code>Host</code> header.
464470
*/
@@ -471,7 +477,9 @@ public interface Context extends Registry {
471477
* @param useProxy True to trust/use the <code>X-Forwarded-Host</code>.
472478
* @return Full/entire request url using the <code>X-Forwarded-Host</code> when present
473479
* or fallback to <code>Host</code> header when missing.
480+
* @deprecated Use {@link ProxyPeerAddressHandler}.
474481
*/
482+
@Deprecated
475483
@Nonnull String getRequestURL(boolean useProxy);
476484

477485
/**
@@ -482,44 +490,95 @@ public interface Context extends Registry {
482490
* @param useProxy True to trust/use the <code>X-Forwarded-Host</code>.
483491
* @return Full/entire request url using the <code>X-Forwarded-Host</code> when present
484492
* or fallback to <code>Host</code> header when missing.
493+
* @deprecated Use {@link ProxyPeerAddressHandler}.
485494
*/
495+
@Deprecated
486496
@Nonnull String getRequestURL(@Nonnull String path, boolean useProxy);
487497

488498
/**
489499
* The IP address of the client or last proxy that sent the request.
490500
*
501+
* If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
502+
* please consider to add {@link ProxyPeerAddressHandler} to your pipeline.
503+
*
491504
* @return The IP address of the client or last proxy that sent the request.
492505
*/
493506
@Nonnull String getRemoteAddress();
494507

508+
/**
509+
* Set IP address of client or last proxy that sent the request.
510+
*
511+
* @param remoteAddress Remote Address.
512+
* @return This context.
513+
*/
514+
@Nonnull Context setRemoteAddress(@Nonnull String remoteAddress);
515+
495516
/**
496517
* Return the host that this request was sent to, in general this will be the
497-
* value of the Host header, minus the port specifier.
518+
* value of the Host header, minus the port specifier. Unless, it is set manually using the
519+
* {@link #setHost(String)} method.
520+
*
521+
* If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
522+
* please consider to add {@link ProxyPeerAddressHandler} to your pipeline.
498523
*
499524
* @return Return the host that this request was sent to, in general this will be the
500525
* value of the Host header, minus the port specifier.
501526
*/
502527
@Nonnull String getHost();
503528

529+
/**
530+
* Set the host (without the port value).
531+
*
532+
* Please keep in mind this method doesn't alter/modify the <code>host</code> header.
533+
*
534+
* @param host Host value.
535+
* @return This context.
536+
*/
537+
@Nonnull Context setHost(@Nonnull String host);
538+
539+
/**
540+
* Return the host and port that this request was sent to, in general this will be the
541+
* value of the Host.
542+
*
543+
* If you run behind a reverse proxy that has been configured to send the X-Forwarded-* header,
544+
* please consider to add {@link ProxyPeerAddressHandler} to your pipeline.
545+
*
546+
* @return Return the host that this request was sent to, in general this will be the
547+
* value of the Host header.
548+
*/
549+
@Nonnull String getHostAndPort();
550+
504551
/**
505552
* Return the host and port that this request was sent to, in general this will be the
506553
* value of the Host or X-Forwarded-Host header.
507554
*
508555
* @param useProxy When true this method looks for host data in the X-Forwarded-Host header.
509556
* @return Return the host that this request was sent to, in general this will be the
510557
* value of the Host header.
558+
* @deprecated Use {@link ProxyPeerAddressHandler}.
511559
*/
512-
@Nullable String getHostAndPort(boolean useProxy);
560+
@Deprecated
561+
@Nonnull String getHostAndPort(boolean useProxy);
513562

514563
/**
515564
* Return the port that this request was sent to. In general this will be the value of the Host
516565
* header, minus the host name.
517566
*
518-
* @return Return the port that this request was sent to. In general this will be the value of the Host
519-
* header, minus the host name.
567+
* If no host header is present, this method returns the value of {@link #getServerPort()}.
568+
*
569+
* @return Return the port that this request was sent to. In general this will be the value of
570+
* the Host header, minus the host name.
520571
*/
521572
int getPort();
522573

574+
/**
575+
* Set port this request was sent to.
576+
*
577+
* @param port Port.
578+
* @return This context.
579+
*/
580+
@Nonnull Context setPort(int port);
581+
523582
/**
524583
* The name of the protocol the request. Always in lower-case.
525584
*
@@ -557,6 +616,14 @@ public interface Context extends Registry {
557616
*/
558617
@Nonnull String getScheme();
559618

619+
/**
620+
* Set HTTP scheme in lower case.
621+
*
622+
* @param scheme HTTP scheme in lower case.
623+
* @return This context.
624+
*/
625+
@Nonnull Context setScheme(@Nonnull String scheme);
626+
560627
/* **********************************************************************************************
561628
* Form API
562629
* **********************************************************************************************

jooby/src/main/java/io/jooby/DefaultContext.java

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -232,30 +232,16 @@ public interface DefaultContext extends Context {
232232
}
233233

234234
@Override default @Nonnull String getRequestURL(@Nonnull String path, boolean useProxy) {
235-
String scheme, hostAndPort;
236-
if (useProxy && !header("X-Forwarded-Host").isMissing()) {
237-
scheme = header("X-Forwarded-Proto").value(getScheme());
238-
hostAndPort = getHostAndPort(true);
239-
} else {
240-
scheme = getScheme();
241-
hostAndPort = getHostAndPort(false);
242-
}
243-
String host, port;
244-
int i = hostAndPort.lastIndexOf(':');
245-
if (i > 0) {
246-
host = hostAndPort.substring(0, i).trim();
247-
port = hostAndPort.substring(i + 1).trim();
248-
} else {
249-
host = hostAndPort;
250-
port = "";
251-
}
235+
String scheme = getScheme();
236+
String host = getHost();
237+
int port = getPort();
252238
StringBuilder url = new StringBuilder();
253239
url.append(scheme).append("://").append(host);
254-
if (port.length() > 0 && !port.equals("80") && !port.equals("443")) {
240+
if (port > 0 && port != PORT && port != SECURE_PORT) {
255241
url.append(":").append(port);
256242
}
257243
if (path == null || path.length() == 0) {
258-
url.append(pathString());
244+
url.append(getRequestPath());
259245
} else {
260246
String contextPath = getContextPath();
261247
if (contextPath.equals("/")) {
@@ -287,6 +273,10 @@ public interface DefaultContext extends Context {
287273
return contentLength.isMissing() ? -1 : contentLength.longValue();
288274
}
289275

276+
@Override default @Nullable String getHostAndPort() {
277+
return getHostAndPort(false);
278+
}
279+
290280
@Override default @Nullable String getHostAndPort(boolean useProxy) {
291281
return header(useProxy ? "X-Forwarded-Host" : "Host").toOptional()
292282
.map(value -> {
@@ -311,7 +301,7 @@ public interface DefaultContext extends Context {
311301
}
312302

313303
@Override default int getPort() {
314-
String hostAndPort = getHostAndPort(false);
304+
String hostAndPort = getHostAndPort();
315305
if (hostAndPort != null) {
316306
int index = hostAndPort.indexOf(':');
317307
if (index > 0) {
@@ -323,7 +313,7 @@ public interface DefaultContext extends Context {
323313
}
324314

325315
@Override default @Nonnull String getHost() {
326-
String hostAndPort = getHostAndPort(false);
316+
String hostAndPort = getHostAndPort();
327317
if (hostAndPort != null) {
328318
int index = hostAndPort.indexOf(':');
329319
return index > 0 ? hostAndPort.substring(0, index).trim() : hostAndPort;

jooby/src/main/java/io/jooby/ForwardingContext.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,20 @@ public ForwardingContext(@Nonnull Context context) {
219219
return ctx.getRemoteAddress();
220220
}
221221

222+
@Nonnull @Override public Context setRemoteAddress(@Nonnull String remoteAddress) {
223+
ctx.setRemoteAddress(remoteAddress);
224+
return this;
225+
}
226+
222227
@Nonnull @Override public String getHost() {
223228
return ctx.getHost();
224229
}
225230

231+
@Nonnull @Override public Context setHost(@Nonnull String host) {
232+
ctx.setHost(host);
233+
return this;
234+
}
235+
226236
@Override public int getServerPort() {
227237
return ctx.getServerPort();
228238
}
@@ -235,6 +245,15 @@ public ForwardingContext(@Nonnull Context context) {
235245
return ctx.getPort();
236246
}
237247

248+
@Nonnull @Override public Context setPort(int port) {
249+
this.ctx.setPort(port);
250+
return this;
251+
}
252+
253+
@Nonnull @Override public String getHostAndPort() {
254+
return ctx.getHostAndPort();
255+
}
256+
238257
@Nullable @Override public String getHostAndPort(boolean useProxy) {
239258
return ctx.getHostAndPort(useProxy);
240259
}
@@ -263,6 +282,11 @@ public ForwardingContext(@Nonnull Context context) {
263282
return ctx.getScheme();
264283
}
265284

285+
@Nonnull @Override public Context setScheme(@Nonnull String scheme) {
286+
this.ctx.setScheme(scheme);
287+
return this;
288+
}
289+
266290
@Override @Nonnull public Formdata form() {
267291
return ctx.form();
268292
}

0 commit comments

Comments
 (0)