• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dhatim / dropwizard-jwt-cookie-authentication / #85

pending completion
#85

push

web-flow
Merge pull request #70 from adamhoward/unauthorized_handler

Make UnauthorizedHandler overridable

5 of 5 new or added lines in 2 files covered. (100.0%)

162 of 193 relevant lines covered (83.94%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/src/main/java/org/dhatim/dropwizard/jwt/cookie/authentication/JwtCookieAuthBundle.java
1
/**
2
 * Copyright 2020 Dhatim
3
 * <p>
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
 * use this file except in compliance with the License. You may obtain a copy of
6
 * the License at
7
 * <p>
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 * <p>
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
 * License for the specific language governing permissions and limitations under
14
 * the License.
15
 */
16
package org.dhatim.dropwizard.jwt.cookie.authentication;
17

18
import com.fasterxml.jackson.databind.module.SimpleModule;
19
import com.google.common.hash.Hashing;
20
import com.google.common.primitives.Ints;
21
import io.dropwizard.Configuration;
22
import io.dropwizard.ConfiguredBundle;
23
import io.dropwizard.auth.AuthDynamicFeature;
24
import io.dropwizard.auth.AuthFilter;
25
import io.dropwizard.auth.AuthValueFactoryProvider;
26
import io.dropwizard.auth.Authorizer;
27
import io.dropwizard.auth.DefaultUnauthorizedHandler;
28
import io.dropwizard.auth.UnauthorizedHandler;
29
import io.dropwizard.jersey.setup.JerseyEnvironment;
30
import io.dropwizard.setup.Bootstrap;
31
import io.dropwizard.setup.Environment;
32
import io.jsonwebtoken.Claims;
33
import io.jsonwebtoken.SignatureAlgorithm;
34
import io.jsonwebtoken.impl.DefaultClaims;
35
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
36

37
import javax.crypto.KeyGenerator;
38
import javax.crypto.spec.SecretKeySpec;
39
import javax.ws.rs.container.ContainerResponseFilter;
40
import java.nio.charset.StandardCharsets;
41
import java.security.Key;
42
import java.security.NoSuchAlgorithmException;
43
import java.time.Duration;
44
import java.util.Optional;
45
import java.util.function.BiFunction;
46
import java.util.function.Function;
47

48
/**
49
 * Dopwizard bundle
50
 *
51
 * @param <C> Your application configuration class
52
 * @param <P> the class of the principal that will be serialized in / deserialized from JWT cookies
53
 */
54
public class JwtCookieAuthBundle<C extends Configuration, P extends JwtCookiePrincipal> implements ConfiguredBundle<C> {
55

56
    public static final String JWT_COOKIE_DEFAULT_NAME = "sessionToken";
57
    private static final String JWT_COOKIE_PREFIX = "jwtCookie";
58

59
    private final Class<P> principalType;
60
    private final Function<P, Claims> serializer;
61
    private final Function<Claims, P> deserializer;
62
    private Function<C, JwtCookieAuthConfiguration> configurationSupplier;
63
    private BiFunction<C, Environment, Key> keySuppplier;
64
    private UnauthorizedHandler unauthorizedHandler;
65

66
    /**
67
     * Get a bundle instance that will use DefaultJwtCookiePrincipal
68
     *
69
     * @param <C> Your application configuration class
70
     * @return a bundle instance that will use DefaultJwtCookiePrincipal
71
     */
72
    public static <C extends Configuration> JwtCookieAuthBundle<C, DefaultJwtCookiePrincipal> getDefault() {
73
        return new JwtCookieAuthBundle<>(
1✔
74
                DefaultJwtCookiePrincipal.class,
75
                DefaultJwtCookiePrincipal::getClaims,
76
                DefaultJwtCookiePrincipal::new);
77
    }
78

79
    /**
80
     * Build a new instance of JwtCookieAuthBundle
81
     *
82
     * @param principalType the class of the principal that will be serialized in / deserialized from JWT cookies
83
     * @param serializer    a function to serialize principals into JWT claims
84
     * @param deserializer  a function to deserialize JWT claims into principals
85
     */
86
    public JwtCookieAuthBundle(Class<P> principalType, Function<P, Claims> serializer, Function<Claims, P> deserializer) {
1✔
87
        this.principalType = principalType;
1✔
88
        this.serializer = serializer;
1✔
89
        this.deserializer = deserializer;
1✔
90
        this.configurationSupplier = c -> new JwtCookieAuthConfiguration();
1✔
91
        this.unauthorizedHandler = new DefaultUnauthorizedHandler();
1✔
92
    }
1✔
93

94
    /**
95
     * If you want to sign the JWT with your own key, specify it here
96
     *
97
     * @param keySupplier a bi-function which will return the signing key from the configuration and environment
98
     * @return this
99
     */
100
    public JwtCookieAuthBundle<C, P> withKeyProvider(BiFunction<C, Environment, Key> keySupplier) {
101
        this.keySuppplier = keySupplier;
×
102
        return this;
×
103
    }
104

105
    /**
106
     * If you need to configure the bundle, specify it here
107
     *
108
     * @param configurationSupplier a bi-function which will return the bundle configuration from the application configuration
109
     * @return this
110
     */
111
    public JwtCookieAuthBundle<C, P> withConfigurationSupplier(Function<C, JwtCookieAuthConfiguration> configurationSupplier) {
112
        this.configurationSupplier = configurationSupplier;
×
113
        return this;
×
114
    }
115

116
    /**
117
     * If you want to use a different unauthorized handler, specify it here
118
     *
119
     * @param unauthorizedHandler an UnauthorizedHandler that will be used whenever a request fails to authenticate
120
     * @return this
121
     */
122
    public JwtCookieAuthBundle<C, P> withUnauthorizedHandler(UnauthorizedHandler unauthorizedHandler) {
123
        this.unauthorizedHandler = unauthorizedHandler;
×
124
        return this;
×
125
    }
126

127

128
    @Override
129
    public void initialize(Bootstrap<?> bootstrap) {
130
        //in case somebody needs to serialize a DefaultJwtCookiePrincipal
131
        bootstrap.getObjectMapper().registerModule(new SimpleModule().addAbstractTypeMapping(Claims.class, DefaultClaims.class));
1✔
132
    }
1✔
133

134
    @Override
135
    public void run(C configuration, Environment environment) throws Exception {
136
        JwtCookieAuthConfiguration conf = configurationSupplier.apply(configuration);
1✔
137

138
        //build the key from the key factory if it was provided
139
        Key key = Optional
1✔
140
                .ofNullable(keySuppplier)
1✔
141
                .map(k -> k.apply(configuration, environment))
1✔
142
                .orElseGet(() -> generateKey(conf.getSecretSeed()));
1✔
143

144
        JerseyEnvironment jerseyEnvironment = environment.jersey();
1✔
145
        jerseyEnvironment.register(new AuthDynamicFeature(getAuthRequestFilter(key, conf.getCookieName())));
1✔
146
        jerseyEnvironment.register(new AuthValueFactoryProvider.Binder<>(principalType));
1✔
147
        jerseyEnvironment.register(RolesAllowedDynamicFeature.class);
1✔
148
        jerseyEnvironment.register(getAuthResponseFilter(key, conf));
1✔
149
        jerseyEnvironment.register(DontRefreshSessionFilter.class);
1✔
150
    }
1✔
151

152
    /**
153
     * Get a filter that will deserialize the principal from JWT cookies found in HTTP requests
154
     *
155
     * @param key        the key used to validate the JWT
156
     * @param cookieName the name of the cookie holding the JWT
157
     * @return the request filter
158
     */
159
    public AuthFilter<String, P> getAuthRequestFilter(Key key, String cookieName) {
160
        return new JwtCookieAuthRequestFilter.Builder()
1✔
161
                .setCookieName(cookieName)
1✔
162
                .setAuthenticator(new JwtCookiePrincipalAuthenticator(key, deserializer))
1✔
163
                .setPrefix(JWT_COOKIE_PREFIX)
1✔
164
                .setAuthorizer((Authorizer<P>) (P::isInRole))
1✔
165
                .setUnauthorizedHandler(unauthorizedHandler)
1✔
166
                .buildAuthFilter();
1✔
167
    }
168

169
    /**
170
     * Get a filter that will deserialize the principal from JWT cookies found in HTTP requests,
171
     * using the default cookie name.
172
     *
173
     * @param key the key used to validate the JWT
174
     * @return the request filter
175
     */
176
    public AuthFilter<String, P> getAuthRequestFilter(Key key) {
177
        return getAuthRequestFilter(key, JWT_COOKIE_DEFAULT_NAME);
×
178
    }
179

180
    /**
181
     * Get a filter that will serialize principals into JWTs and add them to HTTP response cookies
182
     *
183
     * @param key           the key used to sign the JWT
184
     * @param configuration cookie configuration (secure, httpOnly, expiration...)
185
     * @return the response filter
186
     */
187
    public ContainerResponseFilter getAuthResponseFilter(Key key, JwtCookieAuthConfiguration configuration) {
188
        return new JwtCookieAuthResponseFilter<>(
1✔
189
                principalType,
190
                serializer,
191
                configuration.getCookieName(),
1✔
192
                configuration.isSecure(),
1✔
193
                configuration.isHttpOnly(),
1✔
194
                configuration.getDomain(),
1✔
195
                configuration.getSameSite(),
1✔
196
                key,
197
                Ints.checkedCast(Duration.parse(configuration.getSessionExpiryVolatile()).getSeconds()),
1✔
198
                Ints.checkedCast(Duration.parse(configuration.getSessionExpiryPersistent()).getSeconds()));
1✔
199
    }
200

201
    /**
202
     * Generate a HMAC SHA256 Key that can be used to sign JWTs
203
     *
204
     * @param secretSeed a seed from which the key will be generated.
205
     *                   Identical seeds will generate identical keys.
206
     *                   If null, a random key is returned.
207
     * @return a HMAC SHA256 Key
208
     */
209
    public static Key generateKey(String secretSeed) {
210
        // make a key from the seed if it was provided
211
        return Optional.ofNullable(secretSeed)
1✔
212
                .map(seed -> Hashing.sha256().newHasher().putString(seed, StandardCharsets.UTF_8).hash().asBytes())
1✔
213
                .map(k -> (Key) new SecretKeySpec(k, SignatureAlgorithm.HS256.getJcaName()))
1✔
214
                //else generate a random key
215
                .orElseGet(getHmacSha256KeyGenerator()::generateKey);
1✔
216
    }
217

218
    private static KeyGenerator getHmacSha256KeyGenerator() {
219
        try {
220
            return KeyGenerator.getInstance(SignatureAlgorithm.HS256.getJcaName());
1✔
221
        } catch (NoSuchAlgorithmException e) {
×
222
            throw new SecurityException(e);
×
223
        }
224
    }
225
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc