Change the authentication workflow
- The authentication service now exposes a token() method to extract the token from the message - The AuthenticationService#authenticate now accepts the token (extracted using the AuthenticationService#token) - The Realm now exposes a support(AuthenticationToken) method - The authc service will now consult all the realms that support a specific token, and the first realm that successfully authenticate will "win". - Removed the SecurityActionFilter class - it wasn't tested or used anywhere. We'll add a new action filter in a separate commit Original commit: elastic/x-pack-elasticsearch@e4dd36175f
This commit is contained in:
parent
5c2c8d04e9
commit
2d62aee42b
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.support.ActionFilter;
|
||||
import org.elasticsearch.action.support.ActionFilterChain;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SecurityActionFilter implements ActionFilter {
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
private final AuthorizationService authorizationService;
|
||||
|
||||
@Inject
|
||||
public SecurityActionFilter(AuthenticationService authenticationService, AuthorizationService authorizationService) {
|
||||
this.authenticationService = authenticationService;
|
||||
this.authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(String action, ActionRequest actionRequest, ActionListener actionListener, ActionFilterChain actionFilterChain) {
|
||||
User user = authenticationService.authenticate(action, actionRequest);
|
||||
authorizationService.authorize(user, action, actionRequest);
|
||||
actionFilterChain.continueProcessing(action, actionRequest, actionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ package org.elasticsearch.shield.authc;
|
|||
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
/**
|
||||
* Responsible for authenticating the Users behind requests
|
||||
|
@ -15,18 +14,25 @@ import org.elasticsearch.transport.TransportRequest;
|
|||
public interface AuthenticationService {
|
||||
|
||||
/**
|
||||
* Authenticates the user associated with the given request.
|
||||
* Extracts the authenticate token from the given message. If no recognized auth token is associated
|
||||
* with the message, {@code null} is returned.
|
||||
*/
|
||||
AuthenticationToken token(TransportMessage<?> message);
|
||||
|
||||
/**
|
||||
* Authenticates the user associated with the given request based on the given authentication token.
|
||||
*
|
||||
* An {@link AuthenticationToken authentication token} will be extracted from the message, and
|
||||
* will be authenticated. On successful authentication, the {@link org.elasticsearch.shield.User user} that is associated
|
||||
* On successful authentication, the {@link org.elasticsearch.shield.User user} that is associated
|
||||
* with the request (i.e. that is associated with the token's {@link AuthenticationToken#principal() principal})
|
||||
* will be returned.
|
||||
* will be returned. If authentication fails, an {@link AuthenticationException} will be thrown.
|
||||
*
|
||||
* @param action The executed action
|
||||
* @param message The executed message
|
||||
* @param token The authentication token associated with the given request (must not be {@code null})
|
||||
* @return The authenticated User
|
||||
* @throws AuthenticationException If no user could be authenticated (can either be due to missing
|
||||
* supported authentication token, or simply due to bad credentials.
|
||||
*/
|
||||
User authenticate(String action, TransportMessage<?> message) throws AuthenticationException;
|
||||
User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException;
|
||||
|
||||
}
|
||||
|
|
|
@ -28,27 +28,39 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
this.auditTrail = auditTrail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationToken token(TransportMessage<?> message) {
|
||||
for (Realm realm : realms) {
|
||||
AuthenticationToken token = realm.token(message);
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the user associated with the given request by delegating the authentication to
|
||||
* the configured realms. Each realm will be asked to authenticate the request, the first realm that
|
||||
* successfully authenticates will "win" and its authenticated user will be returned. If none of the
|
||||
* configured realms successfully authenticates the request, an {@link AuthenticationException} will
|
||||
* the configured realms. Each realm that supports the given token will be asked to perform authentication,
|
||||
* the first realm that successfully authenticates will "win" and its authenticated user will be returned.
|
||||
* If none of the configured realms successfully authenticates the request, an {@link AuthenticationException} will
|
||||
* be thrown.
|
||||
*
|
||||
* The order by which the realms are ran is based on the order by which they were set in the
|
||||
* constructor.
|
||||
* The order by which the realms are checked is defined in {@link Realms}.
|
||||
*
|
||||
* @param action The executed action
|
||||
* @param message The executed request
|
||||
* @param token The authentication token
|
||||
* @return The authenticated user
|
||||
* @throws AuthenticationException If none of the configured realms successfully authenticated the
|
||||
* request
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public User authenticate(String action, TransportMessage<?> message) throws AuthenticationException {
|
||||
public User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException {
|
||||
assert token != null : "cannot authenticate null tokens";
|
||||
for (Realm realm : realms) {
|
||||
AuthenticationToken token = realm.token(message);
|
||||
if (token != null) {
|
||||
if (realm.supports(token)) {
|
||||
User user = realm.authenticate(token);
|
||||
if (user != null) {
|
||||
return user;
|
||||
|
|
|
@ -31,6 +31,11 @@ public interface Realm<T extends AuthenticationToken> {
|
|||
*/
|
||||
T token(TransportMessage<?> message);
|
||||
|
||||
/**
|
||||
* @return {@code true} if this realm supports the given authentication token, {@code false} otherwise.
|
||||
*/
|
||||
boolean supports(AuthenticationToken token);
|
||||
|
||||
/**
|
||||
* Authenticates the given token. A successful authentication will return the User associated
|
||||
* with the given token. An unsuccessful authentication returns {@code null}.
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.elasticsearch.common.inject.Inject;
|
|||
import org.elasticsearch.common.inject.name.Named;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
import org.elasticsearch.shield.authc.Realm;
|
||||
import org.elasticsearch.shield.authc.support.UserPasswdStore;
|
||||
import org.elasticsearch.shield.authc.support.UserRolesStore;
|
||||
|
@ -45,6 +46,11 @@ public class ESUsersRealm extends AbstractComponent implements Realm<UsernamePas
|
|||
return UsernamePasswordToken.extractToken(message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
return token instanceof UsernamePasswordToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(UsernamePasswordToken token) {
|
||||
if (userPasswdStore == null) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.common.component.AbstractComponent;
|
|||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
import org.elasticsearch.shield.authc.Realm;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
|
@ -35,6 +36,11 @@ public class LdapRealm extends AbstractComponent implements Realm<UsernamePasswo
|
|||
return UsernamePasswordToken.extractToken(message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
return token instanceof UsernamePasswordToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(UsernamePasswordToken token) {
|
||||
return null;
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authc.AuthenticationException;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
import org.elasticsearch.shield.authc.Realm;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
|
||||
|
@ -44,6 +45,11 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
|
|||
return UsernamePasswordToken.extractToken(message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
return token instanceof UsernamePasswordToken;
|
||||
}
|
||||
|
||||
protected final void expire(String username) {
|
||||
if (cache != null) {
|
||||
cache.invalidate(username);
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.authc;
|
||||
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||
|
||||
final Settings settings = ImmutableSettings.EMPTY;
|
||||
final MockRealm goodRealm = new MockRealm("good", true);
|
||||
final MockRealm badRealm = new MockRealm("bad", false);
|
||||
|
||||
InternalAuthenticationService service;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
Realms realms = new Realms(null, null) {
|
||||
@Override
|
||||
Realm[] realms() {
|
||||
return new Realm[] { badRealm, goodRealm };
|
||||
}
|
||||
};
|
||||
service = new InternalAuthenticationService(settings, realms, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToken() throws Exception {
|
||||
AuthenticationToken token = service.token(new MockMessage().putHeader("token", "good"));
|
||||
assertThat(token, notNullValue());
|
||||
assertThat(token, instanceOf(MockRealm.Token.class));
|
||||
assertThat(token.principal(), equalTo("good"));
|
||||
assertThat(badRealm.tokenCalls, equalTo(1));
|
||||
assertThat(goodRealm.tokenCalls, equalTo(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate() throws Exception {
|
||||
User user = service.authenticate("action", new MockMessage(), new MockRealm.Token("good"));
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user, instanceOf(User.Simple.class));
|
||||
assertThat(user.principal(), equalTo("good"));
|
||||
assertThat(badRealm.supportsCallPrincipals.size(), equalTo(1));
|
||||
assertThat(badRealm.supportsCallPrincipals.get(0), equalTo("good"));
|
||||
assertThat(goodRealm.supportsCallPrincipals.size(), equalTo(1));
|
||||
assertThat(goodRealm.supportsCallPrincipals.get(0), equalTo("good"));
|
||||
|
||||
}
|
||||
|
||||
static class MockRealm implements Realm<MockRealm.Token> {
|
||||
|
||||
static class Token implements AuthenticationToken {
|
||||
|
||||
private final String principal;
|
||||
|
||||
Token(String principal) {
|
||||
this.principal = principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String principal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String credentials() {
|
||||
return "changeme";
|
||||
}
|
||||
}
|
||||
|
||||
private final List<String> supportsCallPrincipals = new ArrayList<>();
|
||||
|
||||
private int tokenCalls = 0;
|
||||
|
||||
private final String tokenType;
|
||||
private final boolean good;
|
||||
|
||||
MockRealm(String tokenType, boolean good) {
|
||||
this.tokenType = tokenType;
|
||||
this.good = good;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return "mock";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockRealm.Token token(TransportMessage<?> message) {
|
||||
tokenCalls++;
|
||||
return good ? new Token(tokenType) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
assertThat(token, instanceOf(Token.class));
|
||||
supportsCallPrincipals.add(token.principal());
|
||||
return tokenType.equals(token.principal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(MockRealm.Token token) {
|
||||
return new User.Simple(tokenType);
|
||||
}
|
||||
}
|
||||
|
||||
static class MockMessage extends TransportMessage<MockMessage> {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue