Skip to content

Commit 547f50e

Browse files
committed
Add a new concept of "signer" in order to delegate the process of cookie 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.
1 parent 4773b9e commit 547f50e

File tree

5 files changed

+117
-47
lines changed

5 files changed

+117
-47
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package restx.security;
2+
3+
import javax.inject.Named;
4+
5+
import com.google.common.base.Optional;
6+
7+
import restx.common.Crypto;
8+
import restx.factory.Component;
9+
10+
/**
11+
* Default cookie signer, using HMAC-SHA1 algorithm to sign the cookie.
12+
*
13+
* @author apeyrard
14+
*/
15+
@Component
16+
@Named(RestxSessionCookieFilter.COOKIE_SIGNER_NAME)
17+
public class DefaultCookieSigner implements Signer {
18+
private final SignatureKey signatureKey;
19+
20+
public DefaultCookieSigner(Optional<SignatureKey> signatureKey) {
21+
this.signatureKey = signatureKey.or(SignatureKey.DEFAULT);
22+
}
23+
24+
@Override
25+
public String sign(String cookie) {
26+
return Crypto.sign(cookie, signatureKey.getKey());
27+
}
28+
29+
@Override
30+
public boolean verify(String cookie, String signedCookie) {
31+
return sign(cookie).equals(signedCookie);
32+
}
33+
}

restx-core/src/main/java/restx/security/RestxSessionCookieFilter.java

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
package restx.security;
22

3+
import java.io.IOException;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
7+
import javax.inject.Named;
8+
9+
import org.joda.time.DateTime;
10+
import org.joda.time.Duration;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
314
import com.fasterxml.jackson.core.JsonProcessingException;
415
import com.fasterxml.jackson.databind.ObjectMapper;
516
import com.google.common.base.Optional;
617
import com.google.common.base.Strings;
718
import com.google.common.collect.ImmutableMap;
819
import com.google.common.collect.Maps;
9-
import org.joda.time.DateTime;
10-
import org.joda.time.Duration;
11-
import org.slf4j.Logger;
12-
import org.slf4j.LoggerFactory;
13-
import restx.*;
14-
import restx.common.Crypto;
20+
21+
import restx.AbstractRouteLifecycleListener;
22+
import restx.RestxContext;
23+
import restx.RestxFilter;
24+
import restx.RestxHandler;
25+
import restx.RestxHandlerMatch;
26+
import restx.RestxRequest;
27+
import restx.RestxRequestMatch;
28+
import restx.RestxResponse;
29+
import restx.RouteLifecycleListener;
30+
import restx.StdRestxRequestMatch;
31+
import restx.WebException;
1532
import restx.factory.Component;
1633
import restx.factory.Name;
1734
import restx.http.HttpStatus;
1835
import restx.jackson.FrontObjectMapperFactory;
1936

20-
import javax.inject.Named;
21-
import java.io.IOException;
22-
import java.util.HashMap;
23-
import java.util.Map;
24-
2537
/**
2638
* User: xavierhanin
2739
* Date: 2/8/13
@@ -30,31 +42,33 @@
3042
@Component(priority = -200)
3143
public class RestxSessionCookieFilter implements RestxFilter, RestxHandler {
3244
public static final Name<RestxSessionCookieFilter> NAME = Name.of(RestxSessionCookieFilter.class, "RestxSessionCookieFilter");
45+
public static final String COOKIE_SIGNER_NAME = "CookieSigner";
3346

3447
private static final String EXPIRES = "_expires";
3548

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

3851
private final RestxSession.Definition sessionDefinition;
3952
private final ObjectMapper mapper;
40-
private final SignatureKey signatureKey;
41-
private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
53+
private final Signer signer;
54+
private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
4255
private final RestxSession emptySession;
4356

44-
public RestxSessionCookieFilter(
45-
RestxSession.Definition sessionDefinition,
46-
@Named(FrontObjectMapperFactory.MAPPER_NAME) ObjectMapper mapper,
47-
Optional<SignatureKey> signatureKey,
48-
RestxSessionCookieDescriptor restxSessionCookieDescriptor) {
49-
this.sessionDefinition = sessionDefinition;
50-
this.mapper = mapper;
51-
this.signatureKey = signatureKey.or(SignatureKey.DEFAULT);
52-
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
53-
this.emptySession = new RestxSession(sessionDefinition, ImmutableMap.<String,String>of(),
54-
Optional.<RestxPrincipal>absent(), Duration.ZERO);
55-
}
57+
public RestxSessionCookieFilter(
58+
RestxSession.Definition sessionDefinition,
59+
@Named(FrontObjectMapperFactory.MAPPER_NAME) ObjectMapper mapper,
60+
@Named(COOKIE_SIGNER_NAME) Signer signer,
61+
RestxSessionCookieDescriptor restxSessionCookieDescriptor) {
5662

57-
@Override
63+
this.sessionDefinition = sessionDefinition;
64+
this.mapper = mapper;
65+
this.signer = signer;
66+
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
67+
this.emptySession = new RestxSession(sessionDefinition, ImmutableMap.<String, String>of(),
68+
Optional.<RestxPrincipal>absent(), Duration.ZERO);
69+
}
70+
71+
@Override
5872
public Optional<RestxHandlerMatch> match(RestxRequest req) {
5973
return Optional.of(new RestxHandlerMatch(
6074
new StdRestxRequestMatch("*", req.getRestxPath()),
@@ -96,8 +110,8 @@ public RestxSession buildContextFromRequest(RestxRequest req) throws IOException
96110
return emptySession;
97111
} else {
98112
String sig = req.getCookieValue(restxSessionCookieDescriptor.getCookieSignatureName()).or("");
99-
if (!Crypto.sign(cookie, signatureKey.getKey()).equals(sig)) {
100-
logger.warn("invalid restx session signature. session was: {}. Ignoring session cookie.", cookie);
113+
if (!signer.verify(cookie, sig)) {
114+
logger.warn("invalid restx session signature. session was: {}. Ignoring session cookie.", cookie);
101115
return emptySession;
102116
}
103117
Map<String, String> entries = readEntries(cookie);
@@ -159,8 +173,8 @@ public ImmutableMap<String, String> toCookiesMap(RestxSession session) {
159173
map.put(EXPIRES, DateTime.now().plusDays(30).toString());
160174
String sessionJson = mapper.writeValueAsString(map);
161175
return ImmutableMap.of(restxSessionCookieDescriptor.getCookieName(), sessionJson,
162-
restxSessionCookieDescriptor.getCookieSignatureName(), Crypto.sign(sessionJson, signatureKey.getKey()));
163-
}
176+
restxSessionCookieDescriptor.getCookieSignatureName(), signer.sign(sessionJson));
177+
}
164178
} catch (JsonProcessingException e) {
165179
throw new RuntimeException(e);
166180
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package restx.security;
2+
3+
/**
4+
* Permits to sign and verify messages.
5+
*
6+
* @author apeyrard
7+
*/
8+
public interface Signer {
9+
10+
/**
11+
* Sign the specified message.
12+
* @param message The message to sign.
13+
* @return The signed message.
14+
*/
15+
String sign(String message);
16+
17+
/**
18+
* Verify if the specified message correspond to the signed one.
19+
* @param message The message to verify.
20+
* @param signedMessage The signed message.
21+
* @return True if the message is corresponding to the signed message, false otherwise.
22+
*/
23+
boolean verify(String message, String signedMessage);
24+
}

restx-core/src/main/java/restx/specs/WhenRestxSessionHeaderLoader.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package restx.specs;
22

3-
import com.google.common.base.Optional;
43
import org.slf4j.Logger;
54
import org.slf4j.LoggerFactory;
6-
import restx.security.SignatureKey;
7-
import restx.common.Crypto;
5+
86
import restx.factory.Component;
97
import restx.security.RestxSessionCookieDescriptor;
8+
import restx.security.Signer;
109

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

1918
private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
20-
private final SignatureKey signature;
19+
private final Signer signer;
2120

22-
public WhenRestxSessionHeaderLoader(RestxSessionCookieDescriptor restxSessionCookieDescriptor, Optional<SignatureKey> signature) {
21+
public WhenRestxSessionHeaderLoader(RestxSessionCookieDescriptor restxSessionCookieDescriptor, Signer signer) {
2322
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
24-
this.signature = signature.or(SignatureKey.DEFAULT);
23+
this.signer = signer;
2524
}
2625

2726
@Override
@@ -39,6 +38,6 @@ public void loadHeader(String headerValue, WhenHttpRequest.Builder whenHttpReque
3938
}
4039

4140
whenHttpRequestBuilder.addCookie(restxSessionCookieDescriptor.getCookieName(), sessionContent);
42-
whenHttpRequestBuilder.addCookie(restxSessionCookieDescriptor.getCookieSignatureName(), Crypto.sign(sessionContent, signature.getKey()));
41+
whenHttpRequestBuilder.addCookie(restxSessionCookieDescriptor.getCookieSignatureName(), signer.sign(sessionContent));
4342
}
4443
}

restx-specs-tests/src/main/java/restx/tests/HttpTestClient.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package restx.tests;
22

3+
import static restx.RestxMainRouterFactory.Blade;
4+
5+
import java.util.Map;
6+
7+
import org.joda.time.DateTime;
8+
39
import com.github.kevinsawicki.http.HttpRequest;
410
import com.google.common.collect.ImmutableMap;
5-
import org.joda.time.DateTime;
6-
import restx.RestxMainRouterFactory;
7-
import restx.common.Crypto;
11+
812
import restx.common.UUIDGenerator;
913
import restx.factory.Factory;
1014
import restx.security.RestxSessionCookieDescriptor;
11-
import restx.security.SignatureKey;
12-
13-
import java.util.Map;
14-
15-
import static restx.RestxMainRouterFactory.Blade;
15+
import restx.security.Signer;
1616

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

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

5656
return new HttpTestClient(baseUrl, principal, cookiesBuilder.build());
5757
}

0 commit comments

Comments
 (0)