私はDropwizard 0.4.0を使い始めています。私はHMAC認証に関するいくつかの助けをしたいと思います。誰か助言を得ていますか?Dropwizardを使用してHMAC認証をテストするにはどうすればよいですか?
ありがとうございます。
私はDropwizard 0.4.0を使い始めています。私はHMAC認証に関するいくつかの助けをしたいと思います。誰か助言を得ていますか?Dropwizardを使用してHMAC認証をテストするにはどうすればよいですか?
ありがとうございます。
現在、DropwizardはHMAC認証をサポートしていないため、独自の認証ツールを作成する必要があります。 HMAC認証の一般的な選択は、HTTP Authorizationヘッダーを使用することです。
Authorization: <algorithm> <apiKey> <digest>
例ダイジェストを前HMACとURLエンコードに本体(マーシャリングエンティティ)の内容から構築され
Authorization: HmacSHA1 abcd-efgh-1234 sdafkljlkansdaflk2354jlkj5345345dflkmsdf
あろう:次のコードは、次の形式で、このヘッダが期待しますbase64として追加された共有秘密。 GETやHEADなどの非本体要求の場合、コンテンツは完全なURIパスとみなされ、秘密鍵が付加されたパラメータが使用されます。
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;
}
}
が上記である:それはこのようなものを使用して独自のコードにdropwizard-auth
モジュールに存在BasicAuthenticatorはコードをコピーして、それを修正する必要がありますと
次の手順は、認証プロセスをResourceTest
サブクラスに統合することです。残念ながら、Dropwizardはv0.4.0での認証プロバイダのための優れたエントリー・ポイントを提供していないので、あなたはこれに似た独自の基本クラスで、紹介したいことがあります。ここでも
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"));
}
}
を、上記のコードは、完璧ではありませんアイデアは、擬似的なUserDaoが標準のユーザーに既知の共有秘密鍵を提供できるようにすることです。あなたはテスト目的のために独自のUserBuilder実装を導入する必要があります。
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!");
}
}
はこのように構成されたユニットテストでテストすることができます:
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());
}
}
は、この情報がお役に立てば幸いです。このようなエンドポイントを持っていた上記のコードDropwizardリソースと最後に
、あなたは始まります。
また、http://rubydoc.info/gems/warden-hmac-authentication/0.6.1/file/README.mdも読んでください。これは、署名の前に要求の一部をどのように整理すべきかの定義を提供します。 –
さらに調査を重ねると、JerseyのClientFilter機能を使用して最終リクエストを最後に修正することができます(カスタム承認ヘッダーの追加など)。ここでも、[MultiBit Merchant](https://github.com/gary-rowe/MultiBitMerchant)プロジェクトがこのコードを示しています。 –