Skip to content

Commit 28fabc8

Browse files
committed
add support for HTTP basic authentication (OpenFeign#79)
This changeset adds a simple request interceptor that performs HTTP basic authentication. The HTTP spec isn't very clear on the use of character encodings within this header. The most common interpretation in servers appears to be to expect ISO-8859-1, so I've used that as a default, as well as allowing the encoding to be specified. At @adriancole's suggestion, sun.misc.BASE64Encoder is used for the base64 encoding rather than pulling an implementation into the Util class. If we ever run into a JRE that doesn't provide compatibility with that class, we can eliminate that dependency.
1 parent 2783497 commit 28fabc8

5 files changed

Lines changed: 124 additions & 1 deletion

File tree

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### Version 5.4.0
2+
* Add `BasicAuthRequestInterceptor`
3+
14
### Version 5.3.0
25
* Split `GsonCodec` into `GsonEncoder` and `GsonDecoder`, which are easy to use with `Feign.Builder`
36
* Deprecate `GsonCodec`

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ For further flexibility, you can use Dagger modules directly. See the `Dagger`
5656
When you need to change all requests, regardless of their target, you'll want to configure a `RequestInterceptor`.
5757
For example, if you are acting as an intermediary, you might want to propagate the `X-Forwarded-For` header.
5858

59-
```
59+
```java
6060
static class ForwardedForInterceptor implements RequestInterceptor {
6161
@Override public void apply(RequestTemplate template) {
6262
template.header("X-Forwarded-For", "origin.host.com");
@@ -66,6 +66,12 @@ static class ForwardedForInterceptor implements RequestInterceptor {
6666
Bank bank = Feign.builder().decoder(accountDecoder).requestInterceptor(new ForwardedForInterceptor()).target(Bank.class, "https://api.examplebank.com");
6767
```
6868

69+
Another common example of an interceptor would be authentication, such as using the built-in `BasicAuthRequestInterceptor`.
70+
71+
```java
72+
Bank bank = Feign.builder().decoder(accountDecoder).requestInterceptor(new BasicAuthRequestInterceptor(username, password)).target(Bank.class, "https://api.examplebank.com");
73+
```
74+
6975
### Multiple Interfaces
7076
Feign can produce multiple api interfaces. These are defined as `Target<T>` (default `HardCodedTarget<T>`), which allow for dynamic discovery and decoration of requests prior to execution.
7177

core/src/main/java/feign/Util.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ private Util() { // no instances
6060
* UTF-8: eight-bit UCS Transformation Format.
6161
*/
6262
public static final Charset UTF_8 = Charset.forName("UTF-8");
63+
/**
64+
* ISO-8859-1: ISO Latin Alphabet Number 1 (ISO-LATIN-1).
65+
*/
66+
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
6367

6468
/**
6569
* Copy of {@code com.google.common.base.Preconditions#checkArgument}.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2013 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package feign.auth;
17+
18+
import feign.RequestInterceptor;
19+
import feign.RequestTemplate;
20+
import sun.misc.BASE64Encoder;
21+
22+
import java.nio.charset.Charset;
23+
24+
import static feign.Util.checkNotNull;
25+
import static feign.Util.ISO_8859_1;
26+
27+
/**
28+
* An interceptor that adds the request header needed to use HTTP basic authentication.
29+
*/
30+
public class BasicAuthRequestInterceptor implements RequestInterceptor {
31+
private final String headerValue;
32+
33+
/**
34+
* Creates an interceptor that authenticates all requests with the specified username and password encoded using
35+
* ISO-8859-1.
36+
*
37+
* @param username the username to use for authentication
38+
* @param password the password to use for authentication
39+
*/
40+
public BasicAuthRequestInterceptor(String username, String password) {
41+
this(username, password, ISO_8859_1);
42+
}
43+
44+
/**
45+
* Creates an interceptor that authenticates all requests with the specified username and password encoded using
46+
* the specified charset.
47+
*
48+
* @param username the username to use for authentication
49+
* @param password the password to use for authentication
50+
* @param charset the charset to use when encoding the credentials
51+
*/
52+
public BasicAuthRequestInterceptor(String username, String password, Charset charset) {
53+
checkNotNull(username, "username");
54+
checkNotNull(password, "password");
55+
this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset));
56+
}
57+
58+
@Override public void apply(RequestTemplate template) {
59+
template.header("Authorization", headerValue);
60+
}
61+
62+
/*
63+
* This uses a Sun internal method; if we ever encounter a case where this method is not available, the appropriate
64+
* response would be to pull the necessary portions of Guava's BaseEncoding class into Util.
65+
*/
66+
private static String base64Encode(byte[] bytes) {
67+
return new BASE64Encoder().encode(bytes);
68+
}
69+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2013 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package feign.auth;
17+
18+
import feign.RequestTemplate;
19+
import org.testng.annotations.Test;
20+
21+
import java.util.Collection;
22+
import java.util.Collections;
23+
24+
import static org.testng.Assert.assertEquals;
25+
26+
/**
27+
* Tests for {@link BasicAuthRequestInterceptor}.
28+
*/
29+
public class BasicAuthRequestInterceptorTest {
30+
/**
31+
* Tests that request headers are added as expected.
32+
*/
33+
@Test public void testAuthentication() {
34+
RequestTemplate template = new RequestTemplate();
35+
BasicAuthRequestInterceptor interceptor = new BasicAuthRequestInterceptor("Aladdin", "open sesame");
36+
interceptor.apply(template);
37+
Collection<String> actualValue = template.headers().get("Authorization");
38+
Collection<String> expectedValue = Collections.singletonList("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
39+
assertEquals(actualValue, expectedValue);
40+
}
41+
}

0 commit comments

Comments
 (0)