diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java
new file mode 100644
index 00000000..5b1c2c48
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.vault.authentication;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
+import org.springframework.vault.client.VaultClient;
+import org.springframework.vault.client.VaultException;
+import org.springframework.vault.client.VaultResponseEntity;
+import org.springframework.vault.support.VaultResponse;
+import org.springframework.vault.support.VaultToken;
+
+/**
+ * Cubbyhole {@link ClientAuthentication} implementation.
+ *
+ * Cubbyhole authentication uses Vault primitives to provide a secured authentication workflow. Cubbyhole authentication
+ * uses {@link VaultToken tokens} as primary login method. An ephemeral token is used to obtain a second, login
+ * {@link VaultToken} from Vault's Cubbyhole secret backend. The login token is usually longer-lived and used to
+ * interact with Vault. The login token can be retrieved either from a wrapped response or from the {@code data}
+ * section.
+ *
+ *
+ * @author Mark Paluch
+ * @see CubbyholeAuthenticationOptions
+ * @see Auth Backend: Token
+ * @see Cubbyhole Secret Backend
+ * @see Response Wrapping
+ */
+public class CubbyholeAuthentication implements ClientAuthentication {
+
+ private final static Logger logger = LoggerFactory.getLogger(CubbyholeAuthentication.class);
+
+ private final CubbyholeAuthenticationOptions options;
+
+ private final VaultClient vaultClient;
+
+ /**
+ * Create a new {@link CubbyholeAuthentication} given {@link CubbyholeAuthenticationOptions} and {@link VaultClient}.
+ *
+ * @param options must not be {@literal null}.
+ * @param vaultClient must not be {@literal null}.
+ */
+ public CubbyholeAuthentication(CubbyholeAuthenticationOptions options, VaultClient vaultClient) {
+
+ Assert.notNull(options, "CubbyholeAuthenticationOptions must not be null");
+ Assert.notNull(vaultClient, "VaultClient must not be null");
+
+ this.options = options;
+ this.vaultClient = vaultClient;
+ }
+
+ @Override
+ public VaultToken login() throws VaultException {
+
+ VaultResponseEntity entity = vaultClient.getForEntity(options.getPath(), options.getInitialToken(),
+ VaultResponse.class);
+
+ if (entity.isSuccessful() && entity.hasBody()) {
+
+ VaultResponse body = entity.getBody();
+ Map data = body.getData();
+
+ VaultToken token = getToken(entity, data);
+ if (token != null) {
+
+ logger.debug("Login successful using Cubbyhole authentication");
+ return token;
+ }
+ }
+
+ throw new VaultException(
+ String.format("Cannot retrieve Token from cubbyhole: %s %s", entity.getStatusCode(), entity.getMessage()));
+ }
+
+ private VaultToken getToken(VaultResponseEntity entity, Map data) {
+
+ if (options.isWrappedToken()) {
+
+ VaultResponse response = vaultClient.unwrap((String) data.get("response"), VaultResponse.class);
+ return VaultToken.of((String) response.getAuth().get("client_token"));
+ }
+
+ if (data == null || data.isEmpty()) {
+ throw new VaultException(String
+ .format("Cannot retrieve Token from cubbyhole: Response at %s does not contain a token", entity.getUri()));
+ }
+
+ if (data.size() == 1) {
+ String token = (String) data.get(data.keySet().iterator().next());
+ return VaultToken.of(token);
+ }
+
+ throw new VaultException(String.format(
+ "Cannot retrieve Token from cubbyhole: Response at %s does not contain an unique token", entity.getUri()));
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthenticationOptions.java
new file mode 100644
index 00000000..66bbff4e
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthenticationOptions.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.vault.authentication;
+
+import org.springframework.util.Assert;
+import org.springframework.vault.support.VaultToken;
+
+/**
+ * Authentication options for {@link CubbyholeAuthentication}.
+ *
+ * Authentication options provide the path below cubbyhole and the cubbyhole mode. Instances of this class are immutable
+ * once constructed.
+ *
+ * @author Mark Paluch
+ * @see CubbyholeAuthentication
+ * @see #builder()
+ */
+public class CubbyholeAuthenticationOptions {
+
+ /**
+ * Initial {@link VaultToken} to access Cubbyhole.
+ */
+ private final VaultToken initialToken;
+
+ /**
+ * Path of the Cubbyhole response path.
+ */
+ private final String path;
+
+ /**
+ * Indicates whether the Cubbyhole contains a wrapped token.
+ */
+ private final boolean wrappedToken;
+
+ private CubbyholeAuthenticationOptions(VaultToken initialToken, String path, boolean wrappedToken) {
+
+ this.initialToken = initialToken;
+ this.path = path;
+ this.wrappedToken = wrappedToken;
+ }
+
+ /**
+ * @return a new {@link CubbyholeAuthenticationOptionsBuilder}.
+ */
+ public static CubbyholeAuthenticationOptionsBuilder builder() {
+ return new CubbyholeAuthenticationOptionsBuilder();
+ }
+
+ /**
+ * @return the initial {@link VaultToken} to access Cubbyhole.
+ */
+ public VaultToken getInitialToken() {
+ return initialToken;
+ }
+
+ /**
+ * @return the path of the Cubbyhole response path.
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * @return {@literal true} indicates that the Cubbyhole response contains a wrapped token, otherwise {@literal false}
+ * to expect a token in the {@literal data} response.
+ */
+ public boolean isWrappedToken() {
+ return wrappedToken;
+ }
+
+ /**
+ * Builder for {@link CubbyholeAuthenticationOptions}.
+ */
+ public static class CubbyholeAuthenticationOptionsBuilder {
+
+ private VaultToken initialToken;
+
+ private String path;
+
+ private boolean wrappedToken;
+
+ CubbyholeAuthenticationOptionsBuilder() {}
+
+ /**
+ * Configures the initial {@link VaultToken} to access Cubbyhole.
+ *
+ * @param initialToken must not be {@literal null}.
+ * @return {@code this} {@link CubbyholeAuthenticationOptionsBuilder}.
+ */
+ public CubbyholeAuthenticationOptionsBuilder initialToken(VaultToken initialToken) {
+
+ Assert.notNull(initialToken, "Initial Vault Token must not be null");
+
+ this.initialToken = initialToken;
+ return this;
+ }
+
+ /**
+ * Configures the cubbyhole path, such as {@code cubbyhole/token}. Expects a token in the {@code data} response.
+ *
+ * @param path must not be empty or {@literal null}.
+ * @return {@code this} {@link CubbyholeAuthenticationOptionsBuilder}.
+ */
+ public CubbyholeAuthenticationOptionsBuilder path(String path) {
+
+ Assert.hasText(path, "Path must not be empty");
+
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * Configures whether to use wrapped token responses.
+ *
+ * @return {@code this} {@link CubbyholeAuthenticationOptionsBuilder}.
+ */
+ public CubbyholeAuthenticationOptionsBuilder wrapped() {
+
+ this.path = "cubbyhole/response";
+ this.wrappedToken = true;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link CubbyholeAuthenticationOptions} instance. Requires {@link #path(String)} or
+ * {@link #wrapped()} to be configured.
+ *
+ * @return a new {@link CubbyholeAuthenticationOptions}.
+ */
+ public CubbyholeAuthenticationOptions build() {
+
+ Assert.notNull(initialToken, "Initial Vault Token must not be null");
+ Assert.hasText(path, "Path must not be empty");
+
+ return new CubbyholeAuthenticationOptions(initialToken, path, wrappedToken);
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClient.java b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClient.java
index f75d4a5a..4c2d743c 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClient.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClient.java
@@ -15,13 +15,18 @@
*/
package org.springframework.vault.client;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import java.util.Map;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultToken;
@@ -33,10 +38,9 @@ import org.springframework.web.client.RestTemplate;
* {@link HttpMethod HTTP methods}. {@link VaultClient} is configured with an {@link VaultEndpoint} and
* {@link RestTemplate}. It does not maintain any session or token state. See {@link VaultTemplate} and
* {@link org.springframework.vault.authentication.SessionManager} for authenticated and stateful Vault access.
- *
* {@link VaultClient} encapsulates base URI and path construction and uses {@link VaultAccessor} for request and error
* handling by returning {@link VaultResponseEntity} for requests.
- *
+ *
* @author Mark Paluch
* @see VaultResponseEntity
* @see VaultTemplate
@@ -45,11 +49,13 @@ public class VaultClient extends VaultAccessor {
public static final String VAULT_TOKEN = "X-Vault-Token";
+ private static final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
+
private final VaultEndpoint endpoint;
/**
* Creates a new {@link VaultClient} with a default a {@link RestTemplate} and {@link VaultEndpoint}.
- *
+ *
* @see VaultEndpoint
*/
public VaultClient() {
@@ -99,7 +105,7 @@ public class VaultClient extends VaultAccessor {
/**
* Issue a POST request using the given object to the path, and returns the response as {@link VaultResponseEntity}.
- *
+ *
* @param path the path.
* @param request the Object to be POSTed, may be {@code null}.
* @param responseType the type of the return value
@@ -176,9 +182,8 @@ public class VaultClient extends VaultAccessor {
/**
* Execute the HTTP method to the given URI template, writing the given request entity to the request, and returns the
* response as {@link VaultResponseEntity}.
- *
* URI Template variables are using the given URI variables, if any.
- *
+ *
* @param pathTemplate the path template.
* @param method the HTTP method (GET, POST, etc).
* @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null}.
@@ -198,12 +203,12 @@ public class VaultClient extends VaultAccessor {
* Execute the HTTP method to the given path template, writing the given request entity to the request, and returns
* the response as {@link VaultResponseEntity}. The given {@link ParameterizedTypeReference} is used to pass generic
* type information:
- *
+ *
*