Add support for username/password authentication.
Closes gh-668
This commit is contained in:
committed by
Mark Paluch
parent
0e7b65d5c0
commit
1b33f4e195
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.client.VaultResponses;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.AuthenticationUtil.getLoginPath;
|
||||
|
||||
/**
|
||||
* LDAP implementation of {@link ClientAuthentication}.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
* @see LdapAuthenticationOptions
|
||||
* @see RestOperations
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/ldap">LDAP</a>
|
||||
* @since 2.4
|
||||
*/
|
||||
public class LdapAuthentication implements ClientAuthentication, AuthenticationStepsFactory {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LdapAuthentication.class);
|
||||
|
||||
private final LdapAuthenticationOptions options;
|
||||
|
||||
private final RestOperations restOperations;
|
||||
|
||||
public LdapAuthentication(LdapAuthenticationOptions options, RestOperations restOperations) {
|
||||
Assert.notNull(options, "LdapAuthenticationOptions must not be null");
|
||||
Assert.notNull(restOperations, "RestOperations must not be null");
|
||||
|
||||
this.options = options;
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultToken login() throws VaultException {
|
||||
return createTokenUsingLdapAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationSteps getAuthenticationSteps() {
|
||||
return AuthenticationSteps
|
||||
.fromSupplier(() -> singletonMap("password", options.getPassword()))
|
||||
.login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()));
|
||||
}
|
||||
|
||||
private VaultToken createTokenUsingLdapAuthentication() {
|
||||
try {
|
||||
VaultResponse response = restOperations.postForObject(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()), singletonMap("password", options.getPassword()), VaultResponse.class);
|
||||
|
||||
logger.debug("Login successful using LDAP credentials");
|
||||
|
||||
return LoginTokenUtil.from(response.getAuth());
|
||||
} catch (HttpStatusCodeException e) {
|
||||
throw new VaultException(String.format("Cannot login using LDAP: %s", VaultResponses.getError(e.getResponseBodyAsString())), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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 javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
public class LdapAuthenticationOptions {
|
||||
|
||||
public static final String DEFAULT_LDAP_AUTHENTICATION_PATH = "ldap";
|
||||
|
||||
/**
|
||||
* Path of the ldap authentication backend mount.
|
||||
*/
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Username of the ldap authentication backend mount.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Password of the ldap authentication backend mount.
|
||||
*/
|
||||
private final CharSequence password;
|
||||
|
||||
private LdapAuthenticationOptions(String username, CharSequence password, String path) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static LdapAuthenticationOptionsBuilder builder() {
|
||||
return new LdapAuthenticationOptionsBuilder();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public CharSequence getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public static class LdapAuthenticationOptionsBuilder {
|
||||
|
||||
@Nullable
|
||||
private String username;
|
||||
|
||||
@Nullable
|
||||
private CharSequence password;
|
||||
|
||||
private String path = DEFAULT_LDAP_AUTHENTICATION_PATH;
|
||||
|
||||
LdapAuthenticationOptionsBuilder() {
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptionsBuilder username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptionsBuilder password(CharSequence password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptionsBuilder path(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptions build() {
|
||||
Assert.notNull(this.username, "Username must not be null");
|
||||
Assert.notNull(this.password, "Password must not be null");
|
||||
|
||||
return new LdapAuthenticationOptions(username, password, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.client.VaultResponses;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.AuthenticationUtil.getLoginPath;
|
||||
|
||||
/**
|
||||
* Username and password implementation of {@link ClientAuthentication}.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
* @see UsernamePasswordAuthenticationOptions
|
||||
* @see RestOperations
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/userpass">Username & password</a>
|
||||
* @since 2.4
|
||||
*/
|
||||
public class UsernamePasswordAuthentication implements ClientAuthentication, AuthenticationStepsFactory {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(UsernamePasswordAuthentication.class);
|
||||
|
||||
private final UsernamePasswordAuthenticationOptions options;
|
||||
|
||||
private final RestOperations restOperations;
|
||||
|
||||
public UsernamePasswordAuthentication(UsernamePasswordAuthenticationOptions options, RestOperations restOperations) {
|
||||
Assert.notNull(options, "UsernamePasswordAuthenticationOptions must not be null");
|
||||
Assert.notNull(restOperations, "RestOperations must not be null");
|
||||
|
||||
this.options = options;
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultToken login() throws VaultException {
|
||||
return createTokenUsingUsernamePasswordAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationSteps getAuthenticationSteps() {
|
||||
return AuthenticationSteps
|
||||
.fromSupplier(() -> singletonMap("password", options.getPassword()))
|
||||
.login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()));
|
||||
}
|
||||
|
||||
private VaultToken createTokenUsingUsernamePasswordAuthentication() {
|
||||
try {
|
||||
VaultResponse response = restOperations.postForObject(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()), singletonMap("password", options.getPassword()), VaultResponse.class);
|
||||
|
||||
logger.debug("Login successful using username and password credentials");
|
||||
|
||||
return LoginTokenUtil.from(response.getAuth());
|
||||
} catch (HttpStatusCodeException e) {
|
||||
throw new VaultException(String.format("Cannot login using username and password: %s", VaultResponses.getError(e.getResponseBodyAsString())), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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 javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
public class UsernamePasswordAuthenticationOptions {
|
||||
|
||||
public static final String DEFAULT_USERPASS_AUTHENTICATION_PATH = "userpass";
|
||||
|
||||
/**
|
||||
* Path of the userpass authentication backend mount.
|
||||
*/
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Username of the userpass authentication backend mount.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Password of the userpass authentication backend mount.
|
||||
*/
|
||||
private final CharSequence password;
|
||||
|
||||
private UsernamePasswordAuthenticationOptions(String username, CharSequence password, String path) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public CharSequence getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public static UsernamePasswordAuthenticationBuilder builder() {
|
||||
return new UsernamePasswordAuthenticationBuilder();
|
||||
}
|
||||
|
||||
public static class UsernamePasswordAuthenticationBuilder {
|
||||
|
||||
@Nullable
|
||||
private String username;
|
||||
|
||||
@Nullable
|
||||
private CharSequence password;
|
||||
|
||||
private String path = DEFAULT_USERPASS_AUTHENTICATION_PATH;
|
||||
|
||||
UsernamePasswordAuthenticationBuilder() {
|
||||
}
|
||||
|
||||
public UsernamePasswordAuthenticationBuilder username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UsernamePasswordAuthenticationBuilder password(CharSequence password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UsernamePasswordAuthenticationBuilder path(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UsernamePasswordAuthenticationOptions build() {
|
||||
Assert.notNull(this.username, "Username must not be null");
|
||||
Assert.notNull(this.password, "Password must not be null");
|
||||
|
||||
return new UsernamePasswordAuthenticationOptions(username, password, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2017-2021 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
|
||||
*
|
||||
* https://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.junit.jupiter.api.BeforeEach;
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.LdapAuthenticationOptions.DEFAULT_LDAP_AUTHENTICATION_PATH;
|
||||
import static org.springframework.vault.support.Policy.BuiltinCapabilities.*;
|
||||
|
||||
/**
|
||||
* Integration test base class for {@link LdapAuthentication} tests.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
public abstract class LdapAuthenticationIntegrationTestBase extends IntegrationTestSupport {
|
||||
|
||||
static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build());
|
||||
|
||||
protected final String username = "admin";
|
||||
protected final String password = "qwerty";
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
|
||||
if (!prepare().hasAuth(DEFAULT_LDAP_AUTHENTICATION_PATH)) {
|
||||
prepare().mountAuth(DEFAULT_LDAP_AUTHENTICATION_PATH);
|
||||
}
|
||||
|
||||
prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_LDAP_AUTHENTICATION_PATH, POLICY);
|
||||
prepare().getVaultOperations().doWithSession(restOperations -> restOperations.postForEntity(String.format("auth/%s/users/%s", DEFAULT_LDAP_AUTHENTICATION_PATH, username), singletonMap("password", password), Map.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016-2021 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
|
||||
*
|
||||
* https://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.junit.jupiter.api.Test;
|
||||
import org.springframework.vault.client.VaultClients;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link LdapAuthentication}.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
class LdapAuthenticationIntegrationTests extends LdapAuthenticationIntegrationTestBase {
|
||||
|
||||
@Test
|
||||
void shouldLoginSuccessfully() {
|
||||
|
||||
RestTemplate restTemplate = VaultClients.createRestTemplate();
|
||||
LdapAuthenticationOptions options = LdapAuthenticationOptions.builder()
|
||||
.username(username)
|
||||
.password(password)
|
||||
.build();
|
||||
|
||||
LdapAuthentication authentication = new LdapAuthentication(options, restTemplate);
|
||||
VaultToken login = authentication.login();
|
||||
|
||||
assertThat(login.getToken()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2017-2021 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
|
||||
*
|
||||
* https://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.junit.jupiter.api.BeforeEach;
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.UsernamePasswordAuthenticationOptions.DEFAULT_USERPASS_AUTHENTICATION_PATH;
|
||||
import static org.springframework.vault.support.Policy.BuiltinCapabilities.*;
|
||||
|
||||
/**
|
||||
* Integration test base class for {@link UsernamePasswordAuthentication} tests.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
public abstract class UsernamePasswordAuthenticationIntegrationTestBase extends IntegrationTestSupport {
|
||||
|
||||
static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build());
|
||||
|
||||
protected final String username = "admin";
|
||||
protected final String password = "qwerty";
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
|
||||
if (!prepare().hasAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH)) {
|
||||
prepare().mountAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH);
|
||||
}
|
||||
|
||||
prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_USERPASS_AUTHENTICATION_PATH, POLICY);
|
||||
prepare().getVaultOperations().doWithSession(restOperations -> restOperations.postForEntity(String.format("auth/%s/users/%s", DEFAULT_USERPASS_AUTHENTICATION_PATH, username), singletonMap("password", password), Map.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016-2021 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
|
||||
*
|
||||
* https://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.junit.jupiter.api.Test;
|
||||
import org.springframework.vault.client.VaultClients;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link UsernamePasswordAuthentication}.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
class UsernamePasswordAuthenticationIntegrationTests extends UsernamePasswordAuthenticationIntegrationTestBase {
|
||||
|
||||
@Test
|
||||
void shouldLoginSuccessfully() {
|
||||
|
||||
RestTemplate restTemplate = VaultClients.createRestTemplate();
|
||||
UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder()
|
||||
.username(username)
|
||||
.password(password)
|
||||
.build();
|
||||
|
||||
UsernamePasswordAuthentication authentication = new UsernamePasswordAuthentication(options, restTemplate);
|
||||
VaultToken login = authentication.login();
|
||||
|
||||
assertThat(login.getToken()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user