Skip to content

Commit f64c314

Browse files
author
Olivier Chédru
authored
Make cookie name configurable (#68)
Fixes #67
1 parent 9cda449 commit f64c314

File tree

3 files changed

+76
-41
lines changed

3 files changed

+76
-41
lines changed

src/main/java/org/dhatim/dropwizard/jwt/cookie/authentication/JwtCookieAuthBundle.java

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/**
22
* Copyright 2020 Dhatim
3-
*
3+
* <p>
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
55
* use this file except in compliance with the License. You may obtain a copy of
66
* the License at
7-
*
7+
* <p>
88
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
9+
* <p>
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1212
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -30,40 +30,43 @@
3030
import io.jsonwebtoken.Claims;
3131
import io.jsonwebtoken.SignatureAlgorithm;
3232
import io.jsonwebtoken.impl.DefaultClaims;
33+
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
34+
35+
import javax.crypto.KeyGenerator;
36+
import javax.crypto.spec.SecretKeySpec;
37+
import javax.ws.rs.container.ContainerResponseFilter;
3338
import java.nio.charset.StandardCharsets;
3439
import java.security.Key;
3540
import java.security.NoSuchAlgorithmException;
3641
import java.time.Duration;
3742
import java.util.Optional;
3843
import java.util.function.BiFunction;
3944
import java.util.function.Function;
40-
import javax.crypto.KeyGenerator;
41-
import javax.crypto.spec.SecretKeySpec;
42-
import javax.ws.rs.container.ContainerResponseFilter;
43-
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
4445

4546
/**
4647
* Dopwizard bundle
48+
*
4749
* @param <C> Your application configuration class
4850
* @param <P> the class of the principal that will be serialized in / deserialized from JWT cookies
4951
*/
50-
public class JwtCookieAuthBundle<C extends Configuration, P extends JwtCookiePrincipal> implements ConfiguredBundle<C>{
52+
public class JwtCookieAuthBundle<C extends Configuration, P extends JwtCookiePrincipal> implements ConfiguredBundle<C> {
5153

54+
public static final String JWT_COOKIE_DEFAULT_NAME = "sessionToken";
5255
private static final String JWT_COOKIE_PREFIX = "jwtCookie";
53-
private static final String DEFAULT_COOKIE_NAME = "sessionToken";
5456

5557
private final Class<P> principalType;
56-
private final Function<P,Claims> serializer;
58+
private final Function<P, Claims> serializer;
5759
private final Function<Claims, P> deserializer;
5860
private Function<C, JwtCookieAuthConfiguration> configurationSupplier;
5961
private BiFunction<C, Environment, Key> keySuppplier;
6062

6163
/**
6264
* Get a bundle instance that will use DefaultJwtCookiePrincipal
65+
*
6366
* @param <C> Your application configuration class
6467
* @return a bundle instance that will use DefaultJwtCookiePrincipal
6568
*/
66-
public static <C extends Configuration> JwtCookieAuthBundle<C, DefaultJwtCookiePrincipal> getDefault(){
69+
public static <C extends Configuration> JwtCookieAuthBundle<C, DefaultJwtCookiePrincipal> getDefault() {
6770
return new JwtCookieAuthBundle<>(
6871
DefaultJwtCookiePrincipal.class,
6972
DefaultJwtCookiePrincipal::getClaims,
@@ -72,11 +75,12 @@ public static <C extends Configuration> JwtCookieAuthBundle<C, DefaultJwtCookieP
7275

7376
/**
7477
* Build a new instance of JwtCookieAuthBundle
78+
*
7579
* @param principalType the class of the principal that will be serialized in / deserialized from JWT cookies
76-
* @param serializer a function to serialize principals into JWT claims
77-
* @param deserializer a function to deserialize JWT claims into principals
80+
* @param serializer a function to serialize principals into JWT claims
81+
* @param deserializer a function to deserialize JWT claims into principals
7882
*/
79-
public JwtCookieAuthBundle(Class<P> principalType, Function<P,Claims> serializer, Function<Claims, P> deserializer) {
83+
public JwtCookieAuthBundle(Class<P> principalType, Function<P, Claims> serializer, Function<Claims, P> deserializer) {
8084
this.principalType = principalType;
8185
this.serializer = serializer;
8286
this.deserializer = deserializer;
@@ -85,16 +89,18 @@ public JwtCookieAuthBundle(Class<P> principalType, Function<P,Claims> serializer
8589

8690
/**
8791
* If you want to sign the JWT with your own key, specify it here
92+
*
8893
* @param keySupplier a bi-function which will return the signing key from the configuration and environment
8994
* @return this
9095
*/
91-
public JwtCookieAuthBundle<C, P> withKeyProvider(BiFunction<C, Environment, Key> keySupplier){
96+
public JwtCookieAuthBundle<C, P> withKeyProvider(BiFunction<C, Environment, Key> keySupplier) {
9297
this.keySuppplier = keySupplier;
9398
return this;
9499
}
95100

96101
/**
97102
* If you need to configure the bundle, specify it here
103+
*
98104
* @param configurationSupplier a bi-function which will return the bundle configuration from the application configuration
99105
* @return this
100106
*/
@@ -121,38 +127,52 @@ public void run(C configuration, Environment environment) throws Exception {
121127
.orElseGet(() -> generateKey(conf.getSecretSeed()));
122128

123129
JerseyEnvironment jerseyEnvironment = environment.jersey();
124-
jerseyEnvironment.register(new AuthDynamicFeature(getAuthRequestFilter(key)));
130+
jerseyEnvironment.register(new AuthDynamicFeature(getAuthRequestFilter(key, conf.getCookieName())));
125131
jerseyEnvironment.register(new AuthValueFactoryProvider.Binder<>(principalType));
126132
jerseyEnvironment.register(RolesAllowedDynamicFeature.class);
127133
jerseyEnvironment.register(getAuthResponseFilter(key, conf));
128134
jerseyEnvironment.register(DontRefreshSessionFilter.class);
129135
}
130136

131137
/**
132-
* Get a filter that will desezialize the principal from JWT cookies found in HTTP requests
133-
* @param key the key used to validate the JWT
138+
* Get a filter that will deserialize the principal from JWT cookies found in HTTP requests
139+
*
140+
* @param key the key used to validate the JWT
141+
* @param cookieName the name of the cookie holding the JWT
134142
* @return the request filter
135143
*/
136-
public AuthFilter<String, P> getAuthRequestFilter(Key key){
144+
public AuthFilter<String, P> getAuthRequestFilter(Key key, String cookieName) {
137145
return new JwtCookieAuthRequestFilter.Builder()
138-
.setCookieName(DEFAULT_COOKIE_NAME)
146+
.setCookieName(cookieName)
139147
.setAuthenticator(new JwtCookiePrincipalAuthenticator(key, deserializer))
140148
.setPrefix(JWT_COOKIE_PREFIX)
141-
.setAuthorizer((Authorizer<P>)(P::isInRole))
149+
.setAuthorizer((Authorizer<P>) (P::isInRole))
142150
.buildAuthFilter();
143151
}
144152

153+
/**
154+
* Get a filter that will deserialize the principal from JWT cookies found in HTTP requests,
155+
* using the default cookie name.
156+
*
157+
* @param key the key used to validate the JWT
158+
* @return the request filter
159+
*/
160+
public AuthFilter<String, P> getAuthRequestFilter(Key key) {
161+
return getAuthRequestFilter(key, JWT_COOKIE_DEFAULT_NAME);
162+
}
163+
145164
/**
146165
* Get a filter that will serialize principals into JWTs and add them to HTTP response cookies
147-
* @param key the key used to sign the JWT
166+
*
167+
* @param key the key used to sign the JWT
148168
* @param configuration cookie configuration (secure, httpOnly, expiration...)
149169
* @return the response filter
150170
*/
151171
public ContainerResponseFilter getAuthResponseFilter(Key key, JwtCookieAuthConfiguration configuration) {
152172
return new JwtCookieAuthResponseFilter<>(
153173
principalType,
154174
serializer,
155-
DEFAULT_COOKIE_NAME,
175+
configuration.getCookieName(),
156176
configuration.isSecure(),
157177
configuration.isHttpOnly(),
158178
configuration.getDomain(),
@@ -164,9 +184,10 @@ public ContainerResponseFilter getAuthResponseFilter(Key key, JwtCookieAuthConfi
164184

165185
/**
166186
* Generate a HMAC SHA256 Key that can be used to sign JWTs
187+
*
167188
* @param secretSeed a seed from which the key will be generated.
168-
* Identical seeds will generate identical keys.
169-
* If null, a random key is returned.
189+
* Identical seeds will generate identical keys.
190+
* If null, a random key is returned.
170191
* @return a HMAC SHA256 Key
171192
*/
172193
public static Key generateKey(String secretSeed) {
@@ -178,10 +199,10 @@ public static Key generateKey(String secretSeed) {
178199
.orElseGet(getHmacSha256KeyGenerator()::generateKey);
179200
}
180201

181-
private static KeyGenerator getHmacSha256KeyGenerator(){
182-
try{
202+
private static KeyGenerator getHmacSha256KeyGenerator() {
203+
try {
183204
return KeyGenerator.getInstance(SignatureAlgorithm.HS256.getJcaName());
184-
} catch(NoSuchAlgorithmException e){
205+
} catch (NoSuchAlgorithmException e) {
185206
throw new SecurityException(e);
186207
}
187208
}

src/main/java/org/dhatim/dropwizard/jwt/cookie/authentication/JwtCookieAuthConfiguration.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818
import javax.annotation.Nullable;
1919
import javax.validation.constraints.NotEmpty;
2020

21+
import static org.dhatim.dropwizard.jwt.cookie.authentication.JwtCookieAuthBundle.JWT_COOKIE_DEFAULT_NAME;
22+
2123
/**
2224
* Bundle configuration class
2325
*/
2426
public class JwtCookieAuthConfiguration {
2527

2628
private String secretSeed;
2729

30+
private String cookieName = JWT_COOKIE_DEFAULT_NAME;
31+
2832
private boolean secure = false;
2933

3034
private boolean httpOnly = true;
@@ -51,6 +55,15 @@ public String getSecretSeed() {
5155
return secretSeed;
5256
}
5357

58+
/**
59+
* The name of the cookie holding the JWT. Its default value is "sessionToken".
60+
*
61+
* @return the cookie name
62+
*/
63+
public String getCookieName() {
64+
return cookieName;
65+
}
66+
5467
/**
5568
* Check if the {@code Secure} cookie attribute is set, as described <a href="https://tools.ietf.org/html/rfc6265#section-5.2.5">here</a>.
5669
*

src/test/java/org/dhatim/dropwizard/jwt/cookie/authentication/JwtCookieAuthenticationTest.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
public class JwtCookieAuthenticationTest {
4343

4444
private static final DropwizardAppExtension<Configuration> EXT = new DropwizardAppExtension<Configuration>(TestApplication.class);
45+
private static final String COOKIE_NAME = "sessionToken";
4546

4647
private WebTarget getTarget() {
4748
return EXT.client().target("http://localhost:" + EXT.getLocalPort() + "/application").path("principal");
@@ -63,7 +64,7 @@ public void testCookieSetting() throws IOException {
6364
Assert.assertEquals(principalName, principal.getName());
6465

6566
//check that a session cookie has been set
66-
NewCookie cookie1 = response.getCookies().get("sessionToken");
67+
NewCookie cookie1 = response.getCookies().get(COOKIE_NAME);
6768
Assert.assertNotNull(cookie1);
6869
Assert.assertTrue(Strings.hasText(cookie1.getValue()));
6970
Assert.assertTrue(cookie1.isHttpOnly());
@@ -73,7 +74,7 @@ public void testCookieSetting() throws IOException {
7374
Assert.assertEquals(200, response.getStatus());
7475
principal = getPrincipal(response);
7576
Assert.assertEquals(principalName, principal.getName());
76-
NewCookie cookie2 = response.getCookies().get("sessionToken");
77+
NewCookie cookie2 = response.getCookies().get(COOKIE_NAME);
7778
Assert.assertNotNull(cookie2);
7879
Assert.assertTrue(Strings.hasText(cookie1.getValue()));
7980
Assert.assertNotSame(cookie1.getValue(), cookie2.getValue());
@@ -84,39 +85,39 @@ public void testDontRefreshSession() throws IOException {
8485
//requests made to methods annotated with @DontRefreshSession should not modify the cookie
8586
String principalName = UUID.randomUUID().toString();
8687
Response response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(new DefaultJwtCookiePrincipal(principalName)));
87-
NewCookie cookie = response.getCookies().get("sessionToken");
88+
NewCookie cookie = response.getCookies().get(COOKIE_NAME);
8889

8990
response = getTarget().path("idempotent").request(MediaType.APPLICATION_JSON).cookie(cookie).get();
9091
Assert.assertEquals(200, response.getStatus());
9192
Assert.assertEquals(principalName, getPrincipal(response).getName());
92-
Assert.assertNull(response.getCookies().get("sessionToken"));
93+
Assert.assertNull(response.getCookies().get(COOKIE_NAME));
9394
}
9495

9596
@Test
9697
public void testPublicEndpoint() {
9798
//public endpoints (i.e. not with @Auth, @RolesAllowed etc.) should not modify the cookie
9899
Response response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(new DefaultJwtCookiePrincipal(UUID.randomUUID().toString())));
99-
NewCookie cookie = response.getCookies().get("sessionToken");
100+
NewCookie cookie = response.getCookies().get(COOKIE_NAME);
100101

101102
//request made to public methods should not refresh the cookie
102103
response = getTarget().path("public").request(MediaType.APPLICATION_JSON).cookie(cookie).get();
103104
Assert.assertEquals(200, response.getStatus());
104-
Assert.assertNull(response.getCookies().get("sessionToken"));
105+
Assert.assertNull(response.getCookies().get(COOKIE_NAME));
105106
}
106107

107108
@Test
108109
public void testRememberMe() {
109110
//a volatile principal should set a volatile cookie
110111
DefaultJwtCookiePrincipal principal = new DefaultJwtCookiePrincipal(UUID.randomUUID().toString());
111112
Response response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(principal));
112-
NewCookie cookie = response.getCookies().get("sessionToken");
113+
NewCookie cookie = response.getCookies().get(COOKIE_NAME);
113114
Assert.assertNotNull(cookie);
114115
Assert.assertEquals(-1, cookie.getMaxAge());
115116

116117
//a long term principal should set a persistent cookie
117118
principal.setPersistent(true);
118119
response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(principal));
119-
cookie = response.getCookies().get("sessionToken");
120+
cookie = response.getCookies().get(COOKIE_NAME);
120121
//default maxAge is 604800s (7 days)
121122
Assert.assertNotNull(cookie);
122123
Assert.assertEquals(604800, cookie.getMaxAge());
@@ -132,15 +133,15 @@ public void testRoles() {
132133
//set a principal without the admin role (-> 403 FORBIDDEN)
133134
DefaultJwtCookiePrincipal principal = new DefaultJwtCookiePrincipal(UUID.randomUUID().toString());
134135
response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(principal));
135-
NewCookie cookie = response.getCookies().get("sessionToken");
136+
NewCookie cookie = response.getCookies().get(COOKIE_NAME);
136137
Assert.assertNotNull(cookie);
137138
response = restrictedTarget.request().cookie(cookie).get();
138139
Assert.assertEquals(403, response.getStatus());
139140

140141
//set a principal with the admin role (-> 200 OK)
141142
principal.setRoles(Collections.singleton("admin"));
142143
response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(principal));
143-
cookie = response.getCookies().get("sessionToken");
144+
cookie = response.getCookies().get(COOKIE_NAME);
144145
Assert.assertNotNull(cookie);
145146
response = restrictedTarget.request().cookie(cookie).get();
146147
Assert.assertEquals(200, response.getStatus());
@@ -149,13 +150,13 @@ public void testRoles() {
149150
@Test
150151
public void testDeleteCookie() {
151152
Response response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(new DefaultJwtCookiePrincipal(UUID.randomUUID().toString())));
152-
NewCookie cookie = response.getCookies().get("sessionToken");
153+
NewCookie cookie = response.getCookies().get(COOKIE_NAME);
153154
Assert.assertNotNull(cookie);
154155

155156
//removing the principal should produce a cookie with empty contenant and a past expiration date
156157
response = getTarget().path("unset").request().cookie(cookie).get();
157158
Assert.assertEquals(204, response.getStatus());
158-
cookie = response.getCookies().get("sessionToken");
159+
cookie = response.getCookies().get(COOKIE_NAME);
159160
Assert.assertNotNull(cookie);
160161
Assert.assertEquals("", cookie.getValue());
161162
Assert.assertEquals(Date.from(Instant.EPOCH), cookie.getExpiry());
@@ -166,7 +167,7 @@ public void testGetCurrentPrincipal() throws IOException {
166167
//test to get principal from CurrentPrincipal.get() instead of @Auth
167168
String principalName = UUID.randomUUID().toString();
168169
Response response = getTarget().request(MediaType.APPLICATION_JSON).post(Entity.json(new DefaultJwtCookiePrincipal(principalName)));
169-
NewCookie cookie = response.getCookies().get("sessionToken");
170+
NewCookie cookie = response.getCookies().get(COOKIE_NAME);
170171
Assert.assertNotNull(cookie);
171172

172173
response = getTarget().path("current").request(MediaType.APPLICATION_JSON).cookie(cookie).get();

0 commit comments

Comments
 (0)