Skip to content

Commit d0f5b42

Browse files
committed
Mock Jwt Test Support and Jwt.Builder Polish
Simplified the initial support to introduce fewer classes and only the features described in the ticket. Changed tests to align with existing patterns in the repository. Added JavaDoc to remaining public methods introduced for this feature. Issue: gh-6634 Issue: gh-6851
1 parent e59d8a5 commit d0f5b42

File tree

16 files changed

+791
-719
lines changed

16 files changed

+791
-719
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,20 +15,24 @@
1515
*/
1616
package org.springframework.security.oauth2.jwt;
1717

18-
import java.net.URL;
1918
import java.time.Instant;
2019
import java.util.Collection;
2120
import java.util.Collections;
22-
import java.util.HashMap;
2321
import java.util.LinkedHashMap;
2422
import java.util.Map;
25-
import java.util.stream.Collectors;
26-
import java.util.stream.Stream;
23+
import java.util.function.Consumer;
2724

28-
import org.springframework.security.core.SpringSecurityCoreVersion;
2925
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
3026
import org.springframework.util.Assert;
3127

28+
import static org.springframework.security.oauth2.jwt.JwtClaimNames.AUD;
29+
import static org.springframework.security.oauth2.jwt.JwtClaimNames.EXP;
30+
import static org.springframework.security.oauth2.jwt.JwtClaimNames.IAT;
31+
import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS;
32+
import static org.springframework.security.oauth2.jwt.JwtClaimNames.JTI;
33+
import static org.springframework.security.oauth2.jwt.JwtClaimNames.NBF;
34+
import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB;
35+
3236
/**
3337
* An implementation of an {@link AbstractOAuth2Token} representing a JSON Web Token (JWT).
3438
*
@@ -47,8 +51,6 @@
4751
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516">JSON Web Encryption (JWE)</a>
4852
*/
4953
public class Jwt extends AbstractOAuth2Token implements JwtClaimAccessor {
50-
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
51-
5254
private final Map<String, Object> headers;
5355
private final Map<String, Object> claims;
5456

@@ -88,139 +90,181 @@ public Map<String, Object> getHeaders() {
8890
public Map<String, Object> getClaims() {
8991
return this.claims;
9092
}
91-
92-
public static Builder<?> builder() {
93-
return new Builder<>();
93+
94+
/**
95+
* Return a {@link Jwt.Builder}
96+
*
97+
* @return A {@link Jwt.Builder}
98+
*/
99+
public static Builder withTokenValue(String tokenValue) {
100+
return new Builder(tokenValue);
94101
}
95-
102+
96103
/**
97104
* Helps configure a {@link Jwt}
98105
*
99106
* @author Jérôme Wacongne &lt;ch4mp&#64;c4-soft.com&gt;
107+
* @author Josh Cummings
108+
* @since 5.2
100109
*/
101-
public static class Builder<T extends Builder<T>> {
102-
protected String tokenValue;
103-
protected final Map<String, Object> claims = new HashMap<>();
104-
protected final Map<String, Object> headers = new HashMap<>();
105-
106-
protected Builder() {
107-
}
110+
public final static class Builder {
111+
private String tokenValue;
112+
private final Map<String, Object> claims = new LinkedHashMap<>();
113+
private final Map<String, Object> headers = new LinkedHashMap<>();
108114

109-
public T tokenValue(String tokenValue) {
115+
private Builder(String tokenValue) {
110116
this.tokenValue = tokenValue;
111-
return downcast();
112117
}
113118

114-
public T claim(String name, Object value) {
115-
this.claims.put(name, value);
116-
return downcast();
119+
/**
120+
* Use this token value in the resulting {@link Jwt}
121+
*
122+
* @param tokenValue The token value to use
123+
* @return the {@link Builder} for further configurations
124+
*/
125+
public Builder tokenValue(String tokenValue) {
126+
this.tokenValue = tokenValue;
127+
return this;
117128
}
118129

119-
public T clearClaims(Map<String, Object> claims) {
120-
this.claims.clear();
121-
return downcast();
130+
/**
131+
* Use this claim in the resulting {@link Jwt}
132+
*
133+
* @param name The claim name
134+
* @param value The claim value
135+
* @return the {@link Builder} for further configurations
136+
*/
137+
public Builder claim(String name, Object value) {
138+
this.claims.put(name, value);
139+
return this;
122140
}
123141

124142
/**
125-
* Adds to existing claims (does not replace existing ones)
126-
* @param claims claims to add
127-
* @return this builder to further configure
143+
* Provides access to every {@link #claim(String, Object)}
144+
* declared so far with the possibility to add, replace, or remove.
145+
* @param claimsConsumer the consumer
146+
* @return the {@link Builder} for further configurations
128147
*/
129-
public T claims(Map<String, Object> claims) {
130-
this.claims.putAll(claims);
131-
return downcast();
148+
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
149+
claimsConsumer.accept(this.claims);
150+
return this;
132151
}
133152

134-
public T header(String name, Object value) {
153+
/**
154+
* Use this header in the resulting {@link Jwt}
155+
*
156+
* @param name The header name
157+
* @param value The header value
158+
* @return the {@link Builder} for further configurations
159+
*/
160+
public Builder header(String name, Object value) {
135161
this.headers.put(name, value);
136-
return downcast();
137-
}
138-
139-
public T clearHeaders(Map<String, Object> headers) {
140-
this.headers.clear();
141-
return downcast();
162+
return this;
142163
}
143164

144165
/**
145-
* Adds to existing headers (does not replace existing ones)
146-
* @param headers headers to add
147-
* @return this builder to further configure
166+
* Provides access to every {@link #header(String, Object)}
167+
* declared so far with the possibility to add, replace, or remove.
168+
* @param headersConsumer the consumer
169+
* @return the {@link Builder} for further configurations
148170
*/
149-
public T headers(Map<String, Object> headers) {
150-
headers.entrySet().stream().forEach(e -> this.header(e.getKey(), e.getValue()));
151-
return downcast();
152-
}
153-
154-
public Jwt build() {
155-
final JwtClaimSet claimSet = new JwtClaimSet(claims);
156-
return new Jwt(
157-
this.tokenValue,
158-
claimSet.getClaimAsInstant(JwtClaimNames.IAT),
159-
claimSet.getClaimAsInstant(JwtClaimNames.EXP),
160-
this.headers,
161-
claimSet);
162-
}
163-
164-
public T audience(Stream<String> audience) {
165-
this.claim(JwtClaimNames.AUD, audience.collect(Collectors.toList()));
166-
return downcast();
167-
}
168-
169-
public T audience(Collection<String> audience) {
170-
return audience(audience.stream());
171+
public Builder headers(Consumer<Map<String, Object>> headersConsumer) {
172+
headersConsumer.accept(this.headers);
173+
return this;
171174
}
172175

173-
public T audience(String... audience) {
174-
return audience(Stream.of(audience));
176+
/**
177+
* Use this audience in the resulting {@link Jwt}
178+
*
179+
* @param audience The audience(s) to use
180+
* @return the {@link Builder} for further configurations
181+
*/
182+
public Builder audience(Collection<String> audience) {
183+
return claim(AUD, audience);
175184
}
176185

177-
public T expiresAt(Instant expiresAt) {
178-
this.claim(JwtClaimNames.EXP, expiresAt.getEpochSecond());
179-
return downcast();
186+
/**
187+
* Use this expiration in the resulting {@link Jwt}
188+
*
189+
* @param expiresAt The expiration to use
190+
* @return the {@link Builder} for further configurations
191+
*/
192+
public Builder expiresAt(Instant expiresAt) {
193+
this.claim(EXP, expiresAt);
194+
return this;
180195
}
181196

182-
public T jti(String jti) {
183-
this.claim(JwtClaimNames.JTI, jti);
184-
return downcast();
197+
/**
198+
* Use this identifier in the resulting {@link Jwt}
199+
*
200+
* @param jti The identifier to use
201+
* @return the {@link Builder} for further configurations
202+
*/
203+
public Builder jti(String jti) {
204+
this.claim(JTI, jti);
205+
return this;
185206
}
186207

187-
public T issuedAt(Instant issuedAt) {
188-
this.claim(JwtClaimNames.IAT, issuedAt.getEpochSecond());
189-
return downcast();
208+
/**
209+
* Use this issued-at timestamp in the resulting {@link Jwt}
210+
*
211+
* @param issuedAt The issued-at timestamp to use
212+
* @return the {@link Builder} for further configurations
213+
*/
214+
public Builder issuedAt(Instant issuedAt) {
215+
this.claim(IAT, issuedAt);
216+
return this;
190217
}
191218

192-
public T issuer(URL issuer) {
193-
this.claim(JwtClaimNames.ISS, issuer.toExternalForm());
194-
return downcast();
219+
/**
220+
* Use this issuer in the resulting {@link Jwt}
221+
*
222+
* @param issuer The issuer to use
223+
* @return the {@link Builder} for further configurations
224+
*/
225+
public Builder issuer(String issuer) {
226+
this.claim(ISS, issuer);
227+
return this;
195228
}
196229

197-
public T notBefore(Instant notBefore) {
198-
this.claim(JwtClaimNames.NBF, notBefore.getEpochSecond());
199-
return downcast();
230+
/**
231+
* Use this not-before timestamp in the resulting {@link Jwt}
232+
*
233+
* @param notBefore The not-before timestamp to use
234+
* @return the {@link Builder} for further configurations
235+
*/
236+
public Builder notBefore(Instant notBefore) {
237+
this.claim(NBF, notBefore.getEpochSecond());
238+
return this;
200239
}
201240

202-
public T subject(String subject) {
203-
this.claim(JwtClaimNames.SUB, subject);
204-
return downcast();
205-
}
206-
207-
@SuppressWarnings("unchecked")
208-
protected T downcast() {
209-
return (T) this;
241+
/**
242+
* Use this subject in the resulting {@link Jwt}
243+
*
244+
* @param subject The subject to use
245+
* @return the {@link Builder} for further configurations
246+
*/
247+
public Builder subject(String subject) {
248+
this.claim(SUB, subject);
249+
return this;
210250
}
211-
}
212251

213-
private static final class JwtClaimSet extends HashMap<String, Object> implements JwtClaimAccessor {
214-
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
215-
216-
public JwtClaimSet(Map<String, Object> claims) {
217-
super(claims);
252+
/**
253+
* Build the {@link Jwt}
254+
*
255+
* @return The constructed {@link Jwt}
256+
*/
257+
public Jwt build() {
258+
Instant iat = toInstant(this.claims.get(IAT));
259+
Instant exp = toInstant(this.claims.get(EXP));
260+
return new Jwt(this.tokenValue, iat, exp, this.headers, this.claims);
218261
}
219262

220-
@Override
221-
public Map<String, Object> getClaims() {
222-
return this;
263+
private Instant toInstant(Object timestamp) {
264+
if (timestamp != null) {
265+
Assert.isInstanceOf(Instant.class, timestamp, "timestamps must be of type Instant");
266+
}
267+
return (Instant) timestamp;
223268
}
224-
225269
}
226270
}

0 commit comments

Comments
 (0)