Skip to content

Commit

Permalink
Add a new concept of "signer" in order to delegate the process of coo…
Browse files Browse the repository at this point in the history
…kie signing, and cookie signature check.

The cookie filter will no more do this work, but delegates signing and signature verification to its injected signer.

Create a DefaultCookieSigner component which use the existing SignatureKey in order to stay compatible with previous releases.
The DefaultCookieSigner get the signature key by dependency injection and use the HMAC-SHA1 algorithm (defined in Crypto class) to sign the cookie.
  • Loading branch information
a-peyrard committed Mar 1, 2014
1 parent 4773b9e commit 547f50e
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 47 deletions.
33 changes: 33 additions & 0 deletions restx-core/src/main/java/restx/security/DefaultCookieSigner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package restx.security;

import javax.inject.Named;

import com.google.common.base.Optional;

import restx.common.Crypto;
import restx.factory.Component;

/**
* Default cookie signer, using HMAC-SHA1 algorithm to sign the cookie.
*
* @author apeyrard
*/
@Component
@Named(RestxSessionCookieFilter.COOKIE_SIGNER_NAME)
public class DefaultCookieSigner implements Signer {
private final SignatureKey signatureKey;

public DefaultCookieSigner(Optional<SignatureKey> signatureKey) {
this.signatureKey = signatureKey.or(SignatureKey.DEFAULT);
}

@Override
public String sign(String cookie) {
return Crypto.sign(cookie, signatureKey.getKey());
}

@Override
public boolean verify(String cookie, String signedCookie) {
return sign(cookie).equals(signedCookie);
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
package restx.security;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.inject.Named;

import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import restx.*;
import restx.common.Crypto;

import restx.AbstractRouteLifecycleListener;
import restx.RestxContext;
import restx.RestxFilter;
import restx.RestxHandler;
import restx.RestxHandlerMatch;
import restx.RestxRequest;
import restx.RestxRequestMatch;
import restx.RestxResponse;
import restx.RouteLifecycleListener;
import restx.StdRestxRequestMatch;
import restx.WebException;
import restx.factory.Component;
import restx.factory.Name;
import restx.http.HttpStatus;
import restx.jackson.FrontObjectMapperFactory;

import javax.inject.Named;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
* User: xavierhanin
* Date: 2/8/13
Expand All @@ -30,31 +42,33 @@
@Component(priority = -200)
public class RestxSessionCookieFilter implements RestxFilter, RestxHandler {
public static final Name<RestxSessionCookieFilter> NAME = Name.of(RestxSessionCookieFilter.class, "RestxSessionCookieFilter");
public static final String COOKIE_SIGNER_NAME = "CookieSigner";

private static final String EXPIRES = "_expires";

private final static Logger logger = LoggerFactory.getLogger(RestxSessionCookieFilter.class);

private final RestxSession.Definition sessionDefinition;
private final ObjectMapper mapper;
private final SignatureKey signatureKey;
private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
private final Signer signer;
private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
private final RestxSession emptySession;

public RestxSessionCookieFilter(
RestxSession.Definition sessionDefinition,
@Named(FrontObjectMapperFactory.MAPPER_NAME) ObjectMapper mapper,
Optional<SignatureKey> signatureKey,
RestxSessionCookieDescriptor restxSessionCookieDescriptor) {
this.sessionDefinition = sessionDefinition;
this.mapper = mapper;
this.signatureKey = signatureKey.or(SignatureKey.DEFAULT);
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
this.emptySession = new RestxSession(sessionDefinition, ImmutableMap.<String,String>of(),
Optional.<RestxPrincipal>absent(), Duration.ZERO);
}
public RestxSessionCookieFilter(
RestxSession.Definition sessionDefinition,
@Named(FrontObjectMapperFactory.MAPPER_NAME) ObjectMapper mapper,
@Named(COOKIE_SIGNER_NAME) Signer signer,
RestxSessionCookieDescriptor restxSessionCookieDescriptor) {

@Override
this.sessionDefinition = sessionDefinition;
this.mapper = mapper;
this.signer = signer;
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
this.emptySession = new RestxSession(sessionDefinition, ImmutableMap.<String, String>of(),
Optional.<RestxPrincipal>absent(), Duration.ZERO);
}

@Override
public Optional<RestxHandlerMatch> match(RestxRequest req) {
return Optional.of(new RestxHandlerMatch(
new StdRestxRequestMatch("*", req.getRestxPath()),
Expand Down Expand Up @@ -96,8 +110,8 @@ public RestxSession buildContextFromRequest(RestxRequest req) throws IOException
return emptySession;
} else {
String sig = req.getCookieValue(restxSessionCookieDescriptor.getCookieSignatureName()).or("");
if (!Crypto.sign(cookie, signatureKey.getKey()).equals(sig)) {
logger.warn("invalid restx session signature. session was: {}. Ignoring session cookie.", cookie);
if (!signer.verify(cookie, sig)) {
logger.warn("invalid restx session signature. session was: {}. Ignoring session cookie.", cookie);
return emptySession;
}
Map<String, String> entries = readEntries(cookie);
Expand Down Expand Up @@ -159,8 +173,8 @@ public ImmutableMap<String, String> toCookiesMap(RestxSession session) {
map.put(EXPIRES, DateTime.now().plusDays(30).toString());
String sessionJson = mapper.writeValueAsString(map);
return ImmutableMap.of(restxSessionCookieDescriptor.getCookieName(), sessionJson,
restxSessionCookieDescriptor.getCookieSignatureName(), Crypto.sign(sessionJson, signatureKey.getKey()));
}
restxSessionCookieDescriptor.getCookieSignatureName(), signer.sign(sessionJson));
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Expand Down
24 changes: 24 additions & 0 deletions restx-core/src/main/java/restx/security/Signer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package restx.security;

/**
* Permits to sign and verify messages.
*
* @author apeyrard
*/
public interface Signer {

/**
* Sign the specified message.
* @param message The message to sign.
* @return The signed message.
*/
String sign(String message);

/**
* Verify if the specified message correspond to the signed one.
* @param message The message to verify.
* @param signedMessage The signed message.
* @return True if the message is corresponding to the signed message, false otherwise.
*/
boolean verify(String message, String signedMessage);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package restx.specs;

import com.google.common.base.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import restx.security.SignatureKey;
import restx.common.Crypto;

import restx.factory.Component;
import restx.security.RestxSessionCookieDescriptor;
import restx.security.Signer;

/**
* @author fcamblor
Expand All @@ -17,11 +16,11 @@ public class WhenRestxSessionHeaderLoader implements RestxSpecLoader.WhenHeaderL
private static final Logger logger = LoggerFactory.getLogger(WhenRestxSessionHeaderLoader.class);

private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
private final SignatureKey signature;
private final Signer signer;

public WhenRestxSessionHeaderLoader(RestxSessionCookieDescriptor restxSessionCookieDescriptor, Optional<SignatureKey> signature) {
public WhenRestxSessionHeaderLoader(RestxSessionCookieDescriptor restxSessionCookieDescriptor, Signer signer) {
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
this.signature = signature.or(SignatureKey.DEFAULT);
this.signer = signer;
}

@Override
Expand All @@ -39,6 +38,6 @@ public void loadHeader(String headerValue, WhenHttpRequest.Builder whenHttpReque
}

whenHttpRequestBuilder.addCookie(restxSessionCookieDescriptor.getCookieName(), sessionContent);
whenHttpRequestBuilder.addCookie(restxSessionCookieDescriptor.getCookieSignatureName(), Crypto.sign(sessionContent, signature.getKey()));
whenHttpRequestBuilder.addCookie(restxSessionCookieDescriptor.getCookieSignatureName(), signer.sign(sessionContent));
}
}
20 changes: 10 additions & 10 deletions restx-specs-tests/src/main/java/restx/tests/HttpTestClient.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package restx.tests;

import static restx.RestxMainRouterFactory.Blade;

import java.util.Map;

import org.joda.time.DateTime;

import com.github.kevinsawicki.http.HttpRequest;
import com.google.common.collect.ImmutableMap;
import org.joda.time.DateTime;
import restx.RestxMainRouterFactory;
import restx.common.Crypto;

import restx.common.UUIDGenerator;
import restx.factory.Factory;
import restx.security.RestxSessionCookieDescriptor;
import restx.security.SignatureKey;

import java.util.Map;

import static restx.RestxMainRouterFactory.Blade;
import restx.security.Signer;

/**
* HttpTestClient is a helper to create com.github.kevinsawicki.http.HttpRequest
Expand Down Expand Up @@ -43,15 +43,15 @@ private HttpTestClient(String baseUrl, String principal, ImmutableMap<String, St
public HttpTestClient authenticatedAs(String principal) {
Factory factory = Factory.newInstance();
RestxSessionCookieDescriptor restxSessionCookieDescriptor = factory.getComponent(RestxSessionCookieDescriptor.class);
SignatureKey signature = factory.queryByClass(SignatureKey.class).findOneAsComponent().or(SignatureKey.DEFAULT);
Signer signer = factory.queryByClass(Signer.class).findOneAsComponent().get();

ImmutableMap.Builder<String, String> cookiesBuilder = ImmutableMap.<String, String>builder().putAll(cookies);
String uuid = factory.getComponent(UUIDGenerator.class).doGenerate();
String expires = DateTime.now().plusHours(1).toString();
String sessionContent = String.format(
"{\"_expires\":\"%s\",\"principal\":\"%s\",\"sessionKey\":\"%s\"}", expires, principal, uuid);
cookiesBuilder.put(restxSessionCookieDescriptor.getCookieName(), sessionContent);
cookiesBuilder.put(restxSessionCookieDescriptor.getCookieSignatureName(), Crypto.sign(sessionContent, signature.getKey()));
cookiesBuilder.put(restxSessionCookieDescriptor.getCookieSignatureName(), signer.sign(sessionContent));

return new HttpTestClient(baseUrl, principal, cookiesBuilder.build());
}
Expand Down

0 comments on commit 547f50e

Please sign in to comment.