Ich beginne gerade mit Dropwizard 0.4.0, und ich möchte etwas Hilfe mit HMAC-Authentifizierung. Hat jemand einen Rat bekommen?Wie kann ich HMAC Authentifizierung mit Dropwizard testen?
Vielen Dank im Voraus.
Ich beginne gerade mit Dropwizard 0.4.0, und ich möchte etwas Hilfe mit HMAC-Authentifizierung. Hat jemand einen Rat bekommen?Wie kann ich HMAC Authentifizierung mit Dropwizard testen?
Vielen Dank im Voraus.
Gegenwärtig unterstützt Dropwizard die HMAC-Authentifizierung nicht direkt, so dass Sie Ihren eigenen Authentifikator schreiben müssen. Eine typische Wahl für die HMAC-Authentifizierung ist die Verwendung des HTTP-Autorisierungs-Headers. Der folgende Code erwartet, diesen Header in dem folgende Format:
Authorization: <algorithm> <apiKey> <digest>
Ein Beispiel
Authorization: HmacSHA1 abcd-efgh-1234 sdafkljlkansdaflk2354jlkj5345345dflkmsdf
Der Verdau wird aus dem Inhalt der Körper (vermarshallten Entität) vor der URL-Codierung mit der HMAC gebaut werden würde shared secret als base64 angehängt. Bei einer Nichtkörperanforderung wie GET oder HEAD wird der Inhalt als vollständiger URI-Pfad und als Parameter mit dem angehängten geheimen Schlüssel verwendet.
Um dies in einer Weise umzusetzen, dass Dropwizard arbeiten können, damit Sie erfordert die BasicAuthenticator Code in der dropwizard-auth
Modul in Ihrem eigenen Code und ändern Sie es mit so etwas zu kopieren:
import com.google.common.base.Optional;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.yammer.dropwizard.auth.AuthenticationException;
import com.yammer.dropwizard.auth.Authenticator;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
class HmacAuthInjectable<T> extends AbstractHttpContextInjectable<T> {
private static final String PREFIX = "HmacSHA1";
private static final String HEADER_VALUE = PREFIX + " realm=\"%s\"";
private final Authenticator<HmacCredentials, T> authenticator;
private final String realm;
private final boolean required;
HmacAuthInjectable(Authenticator<HmacCredentials, T> authenticator, String realm, boolean required) {
this.authenticator = authenticator;
this.realm = realm;
this.required = required;
}
public Authenticator<HmacCredentials, T> getAuthenticator() {
return authenticator;
}
public String getRealm() {
return realm;
}
public boolean isRequired() {
return required;
}
@Override
public T getValue(HttpContext c) {
try {
final String header = c.getRequest().getHeaderValue(HttpHeaders.AUTHORIZATION);
if (header != null) {
final String[] authTokens = header.split(" ");
if (authTokens.length != 3) {
// Malformed
HmacAuthProvider.LOG.debug("Error decoding credentials (length is {})", authTokens.length);
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
final String algorithm = authTokens[0];
final String apiKey = authTokens[1];
final String signature = authTokens[2];
final String contents;
// Determine which part of the request will be used for the content
final String method = c.getRequest().getMethod().toUpperCase();
if ("GET".equals(method) ||
"HEAD".equals(method) ||
"DELETE".equals(method)) {
// No entity so use the URI
contents = c.getRequest().getRequestUri().toString();
} else {
// Potentially have an entity (even in OPTIONS) so use that
contents = c.getRequest().getEntity(String.class);
}
final HmacCredentials credentials = new HmacCredentials(algorithm, apiKey, signature, contents);
final Optional<T> result = authenticator.authenticate(credentials);
if (result.isPresent()) {
return result.get();
}
}
} catch (IllegalArgumentException e) {
HmacAuthProvider.LOG.debug(e, "Error decoding credentials");
} catch (AuthenticationException e) {
HmacAuthProvider.LOG.warn(e, "Error authenticating credentials");
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
if (required) {
throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.AUTHORIZATION,
String.format(HEADER_VALUE, realm))
.entity("Credentials are required to access this resource.")
.type(MediaType.TEXT_PLAIN_TYPE)
.build());
}
return null;
}
}
Die oben ist nicht perfekt, aber es wird dich beginnen. Sie können sich auf die MultiBit Merchant release candidate source code (MIT-Lizenz) für eine aktuellere Version und die verschiedenen unterstützenden Klassen beziehen. Der nächste Schritt besteht darin, den Authentifizierungsprozess in Ihre Unterklasse zu integrieren. Leider hat Dropwizard keinen guten Einstiegspunkt für Authentifizierungsanbieter in v0.4.0 bieten, so können Sie Ihre eigene Basisklasse, die ähnlich wie diese einführen wollen:
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.test.framework.AppDescriptor;
import com.sun.jersey.test.framework.JerseyTest;
import com.sun.jersey.test.framework.LowLevelAppDescriptor;
import com.xeiam.xchange.utils.CryptoUtils;
import com.yammer.dropwizard.bundles.JavaBundle;
import com.yammer.dropwizard.jersey.DropwizardResourceConfig;
import com.yammer.dropwizard.jersey.JacksonMessageBodyProvider;
import com.yammer.dropwizard.json.Json;
import org.codehaus.jackson.map.Module;
import org.junit.After;
import org.junit.Before;
import org.multibit.mbm.auth.hmac.HmacAuthProvider;
import org.multibit.mbm.auth.hmac.HmacAuthenticator;
import org.multibit.mbm.persistence.dao.UserDao;
import org.multibit.mbm.persistence.dto.User;
import org.multibit.mbm.persistence.dto.UserBuilder;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Set;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* A base test class for testing Dropwizard resources.
*/
public abstract class BaseResourceTest {
private final Set<Object> singletons = Sets.newHashSet();
private final Set<Object> providers = Sets.newHashSet();
private final List<Module> modules = Lists.newArrayList();
private JerseyTest test;
protected abstract void setUpResources() throws Exception;
protected void addResource(Object resource) {
singletons.add(resource);
}
public void addProvider(Object provider) {
providers.add(provider);
}
protected void addJacksonModule(Module module) {
modules.add(module);
}
protected Json getJson() {
return new Json();
}
protected Client client() {
return test.client();
}
@Before
public void setUpJersey() throws Exception {
setUpResources();
this.test = new JerseyTest() {
@Override
protected AppDescriptor configure() {
final DropwizardResourceConfig config = new DropwizardResourceConfig();
for (Object provider : JavaBundle.DEFAULT_PROVIDERS) { // sorry, Scala folks
config.getSingletons().add(provider);
}
for (Object provider : providers) {
config.getSingletons().add(provider);
}
Json json = getJson();
for (Module module : modules) {
json.registerModule(module);
}
config.getSingletons().add(new JacksonMessageBodyProvider(json));
config.getSingletons().addAll(singletons);
return new LowLevelAppDescriptor.Builder(config).build();
}
};
test.setUp();
}
@After
public void tearDownJersey() throws Exception {
if (test != null) {
test.tearDown();
}
}
/**
* @param contents The content to sign with the default HMAC process (POST body, GET resource path)
* @return
*/
protected String buildHmacAuthorization(String contents, String apiKey, String secretKey) throws UnsupportedEncodingException, GeneralSecurityException {
return String.format("HmacSHA1 %s %s",apiKey, CryptoUtils.computeSignature("HmacSHA1", contents, secretKey));
}
protected void setUpAuthenticator() {
User user = UserBuilder
.getInstance()
.setUUID("abc123")
.setSecretKey("def456")
.build();
//
UserDao userDao = mock(UserDao.class);
when(userDao.getUserByUUID("abc123")).thenReturn(user);
HmacAuthenticator authenticator = new HmacAuthenticator();
authenticator.setUserDao(userDao);
addProvider(new HmacAuthProvider<User>(authenticator, "REST"));
}
}
Auch hier ist der obige Code nicht perfekt, Die Idee besteht jedoch darin, es Userdao zu ermöglichen, einem Standardbenutzer einen bekannten gemeinsamen geheimen Schlüssel zur Verfügung zu stellen. Sie müssen Ihre eigene UserBuilder-Implementierung zu Testzwecken einführen.
schließlich mit dem obigen Code einer Dropwizard Ressource, die einen Endpunkt wie folgt hatte:
import com.google.common.base.Optional;
import com.yammer.dropwizard.auth.Auth;
import com.yammer.metrics.annotation.Timed;
import org.multibit.mbm.core.Saying;
import org.multibit.mbm.persistence.dto.User;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.atomic.AtomicLong;
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public class HelloWorldResource {
private final String template;
private final String defaultName;
private final AtomicLong counter;
public HelloWorldResource(String template, String defaultName) {
this.template = template;
this.defaultName = defaultName;
this.counter = new AtomicLong();
}
@GET
@Timed
@Path("/hello-world")
public Saying sayHello(@QueryParam("name") Optional<String> name) {
return new Saying(counter.incrementAndGet(),
String.format(template, name.or(defaultName)));
}
@GET
@Timed
@Path("/secret")
public Saying saySecuredHello(@Auth User user) {
return new Saying(counter.incrementAndGet(),
"You cracked the code!");
}
}
mit einem Unit-Test getestet werden kann, der so konfiguriert wurde:
import org.junit.Test;
import org.multibit.mbm.core.Saying;
import org.multibit.mbm.test.BaseResourceTest;
import javax.ws.rs.core.HttpHeaders;
import static org.junit.Assert.assertEquals;
public class HelloWorldResourceTest extends BaseResourceTest {
@Override
protected void setUpResources() {
addResource(new HelloWorldResource("Hello, %s!","Stranger"));
setUpAuthenticator();
}
@Test
public void simpleResourceTest() throws Exception {
Saying expectedSaying = new Saying(1,"Hello, Stranger!");
Saying actualSaying = client()
.resource("/hello-world")
.get(Saying.class);
assertEquals("GET hello-world returns a default",expectedSaying.getContent(),actualSaying.getContent());
}
@Test
public void hmacResourceTest() throws Exception {
String authorization = buildHmacAuthorization("/secret", "abc123", "def456");
Saying actual = client()
.resource("/secret")
.header(HttpHeaders.AUTHORIZATION, authorization)
.get(Saying.class);
assertEquals("GET secret returns unauthorized","You cracked the code!", actual.getContent());
}
}
this helps Du fängst an
Vielleicht möchten Sie auch lesen: http://rubydoc.info/gems/warden-hmac-authentication/0.6.1/file/README.md. Es bietet eine Definition, wie Teile der Anfrage vor der Unterzeichnung angeordnet werden sollten. –
Und nach mehr Forschung werden Sie wahrscheinlich die ClientFilter-Einrichtung von Jersey verwenden wollen, um letzte Änderungen an der endgültigen Anfrage vorzunehmen (z. B. Hinzufügen Ihres benutzerdefinierten Autorisierungs-Headers). Das Projekt [MultiBit Merchant] (https://github.com/gary-rowe/MultiBitMerchant) demonstriert diesen Code erneut. –