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:
uboness 2014-08-12 16:26:46 +02:00
parent 5c2c8d04e9
commit 2d62aee42b
8 changed files with 180 additions and 55 deletions

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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}.

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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> {
}
}