Skip to content

Commit 29fb53c

Browse files
committed
#211 - Prototype work to support custom Claim Converters.
1 parent f81bbe5 commit 29fb53c

File tree

3 files changed

+350
-0
lines changed

3 files changed

+350
-0
lines changed

implementation/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@
117117
<artifactId>smallrye-config</artifactId>
118118
<scope>test</scope>
119119
</dependency>
120+
<dependency>
121+
<groupId>io.smallrye.converters</groupId>
122+
<artifactId>smallrye-converters</artifactId>
123+
<version>1.0.0-SNAPSHOT</version>
124+
<scope>test</scope>
125+
</dependency>
120126
</dependencies>
121127

122128
<build>
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
package io.smallrye.jwt.auth.cdi;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import java.lang.annotation.Annotation;
6+
import java.lang.reflect.Member;
7+
import java.lang.reflect.Type;
8+
import java.util.Collections;
9+
import java.util.HashSet;
10+
import java.util.Set;
11+
12+
import javax.enterprise.context.Dependent;
13+
import javax.enterprise.context.RequestScoped;
14+
import javax.enterprise.context.spi.CreationalContext;
15+
import javax.enterprise.inject.Default;
16+
import javax.enterprise.inject.Produces;
17+
import javax.enterprise.inject.spi.Annotated;
18+
import javax.enterprise.inject.spi.Bean;
19+
import javax.enterprise.inject.spi.BeanManager;
20+
import javax.enterprise.inject.spi.CDI;
21+
import javax.enterprise.inject.spi.InjectionPoint;
22+
import javax.enterprise.inject.spi.PassivationCapable;
23+
import javax.enterprise.util.AnnotationLiteral;
24+
import javax.inject.Inject;
25+
26+
import org.eclipse.microprofile.jwt.Claim;
27+
import org.eclipse.microprofile.jwt.ClaimValue;
28+
import org.eclipse.microprofile.jwt.Claims;
29+
import org.eclipse.microprofile.jwt.JsonWebToken;
30+
import org.jboss.weld.junit4.WeldInitiator;
31+
import org.jose4j.jws.JsonWebSignature;
32+
import org.jose4j.jwt.JwtClaims;
33+
import org.junit.Rule;
34+
import org.junit.Test;
35+
36+
import io.smallrye.converters.SmallRyeConvertersBuilder;
37+
import io.smallrye.converters.api.Converters;
38+
import io.smallrye.jwt.KeyUtils;
39+
import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal;
40+
import io.smallrye.jwt.build.Jwt;
41+
42+
@SuppressWarnings("CdiUnproxyableBeanTypesInspection")
43+
public class ClaimConverterTest {
44+
@Rule
45+
public WeldInitiator weld = WeldInitiator.from(
46+
ClaimInjectionBean.class,
47+
ClaimConverterTest.class,
48+
RawConverterBean.class,
49+
ClaimConverterBean.class,
50+
ClaimValueProducer.class,
51+
CommonJwtProducer.class)
52+
.addBeans()
53+
.activate(RequestScoped.class)
54+
// This will auto register with an Extension and the Type of the injection Point.
55+
.addBeans(new ClaimInjectionBean<>(String.class))
56+
.addBeans(new ClaimInjectionBean<>(Byte.class))
57+
.addBeans(new ClaimInjectionBean<>(Short.class))
58+
.addBeans(new ClaimInjectionBean<>(Integer.class))
59+
.addBeans(new ClaimInjectionBean<>(Long.class))
60+
.addBeans(new ClaimInjectionBean<>(Float.class))
61+
.addBeans(new ClaimInjectionBean<>(Double.class))
62+
.addBeans(new ClaimInjectionBean<>(Boolean.class))
63+
.addBeans(new ClaimInjectionBean<>(Character.class))
64+
.inject(this)
65+
.build();
66+
67+
@Inject
68+
private JsonWebToken jsonWebToken;
69+
@Inject
70+
private RawConverterBean raw;
71+
@Inject
72+
private ClaimConverterBean claim;
73+
74+
@Test
75+
public void convertString() {
76+
assertEquals("jdoe", raw.getName());
77+
assertEquals("jdoe", claim.getName().getValue());
78+
assertEquals("jdoe", jsonWebToken.<String> getClaim("preferred_username"));
79+
}
80+
81+
@Test
82+
public void convertRawWrapperTypes() {
83+
assertEquals(1, raw.getByteClaim().byteValue());
84+
assertEquals(9, raw.getShortClaim().shortValue());
85+
assertEquals(99, raw.getIntegerClaim().intValue());
86+
assertEquals(999, raw.getLongClaim().longValue());
87+
assertEquals(99.9, raw.getFloatClaim(), 0.001);
88+
assertEquals(99.99, raw.getDoubeClaim(), 0.001);
89+
assertEquals(true, raw.getBooleanClaim());
90+
}
91+
92+
@Produces
93+
@RequestScoped
94+
private static JsonWebToken jwt() throws Exception {
95+
String jwt = Jwt.claims("/token-converter.json").sign();
96+
JsonWebSignature jws = new JsonWebSignature();
97+
jws.setKey(KeyUtils.readPublicKey("/publicKey.pem"));
98+
jws.setCompactSerialization(jwt);
99+
JwtClaims claims = JwtClaims.parse(jws.getPayload());
100+
return new DefaultJWTCallerPrincipal(jwt, claims);
101+
}
102+
103+
@RequestScoped
104+
private static class RawConverterBean {
105+
@Inject
106+
@Claim("preferred_username")
107+
private String name;
108+
@Inject
109+
@Claim("byte")
110+
private Byte byteClaim;
111+
@Inject
112+
@Claim("short")
113+
private Short shortClaim;
114+
@Inject
115+
@Claim("integer")
116+
private Integer integerClaim;
117+
@Inject
118+
@Claim("float")
119+
private Float floatClaim;
120+
@Inject
121+
@Claim("double")
122+
private Double doubeClaim;
123+
@Inject
124+
@Claim("boolean")
125+
private Boolean booleanClaim;
126+
@Inject
127+
@Claim("long")
128+
private Long longClaim;
129+
130+
String getName() {
131+
return name;
132+
}
133+
134+
public Byte getByteClaim() {
135+
return byteClaim;
136+
}
137+
138+
public Short getShortClaim() {
139+
return shortClaim;
140+
}
141+
142+
public Integer getIntegerClaim() {
143+
return integerClaim;
144+
}
145+
146+
public Float getFloatClaim() {
147+
return floatClaim;
148+
}
149+
150+
public Double getDoubeClaim() {
151+
return doubeClaim;
152+
}
153+
154+
public Boolean getBooleanClaim() {
155+
return booleanClaim;
156+
}
157+
158+
public Long getLongClaim() {
159+
return longClaim;
160+
}
161+
}
162+
163+
@RequestScoped
164+
private static class ClaimConverterBean {
165+
@Inject
166+
@Claim("preferred_username")
167+
private ClaimValue<String> name;
168+
169+
ClaimValue<String> getName() {
170+
return name;
171+
}
172+
}
173+
174+
private static class ClaimInjectionBean<T> implements Bean<T>, PassivationCapable {
175+
private final Class klass;
176+
private final Converters converters;
177+
178+
public ClaimInjectionBean(final Class klass) {
179+
this.klass = klass;
180+
this.converters = new SmallRyeConvertersBuilder().build();
181+
}
182+
183+
@Override
184+
public Class<?> getBeanClass() {
185+
return ClaimInjectionBean.class;
186+
}
187+
188+
@Override
189+
public Set<InjectionPoint> getInjectionPoints() {
190+
return new HashSet<>();
191+
}
192+
193+
@Override
194+
public boolean isNullable() {
195+
return false;
196+
}
197+
198+
@Override
199+
public T create(final CreationalContext<T> creationalContext) {
200+
final JsonWebToken jsonWebToken = CDI.current().select(JsonWebToken.class).get();
201+
if (jsonWebToken == null) {
202+
return null;
203+
}
204+
205+
final BeanManager beanManager = CDI.current().getBeanManager();
206+
final InjectionPoint injectionPoint = (InjectionPoint) beanManager.getInjectableReference(new InjectionPoint() {
207+
@Override
208+
public Type getType() {
209+
return InjectionPoint.class;
210+
}
211+
212+
@Override
213+
public Set<Annotation> getQualifiers() {
214+
return Collections.<Annotation> singleton(new AnnotationLiteral<Default>() {
215+
});
216+
}
217+
218+
@Override
219+
public Bean<?> getBean() {
220+
return null;
221+
}
222+
223+
@Override
224+
public Member getMember() {
225+
return null;
226+
}
227+
228+
@Override
229+
public Annotated getAnnotated() {
230+
return null;
231+
}
232+
233+
@Override
234+
public boolean isDelegate() {
235+
return false;
236+
}
237+
238+
@Override
239+
public boolean isTransient() {
240+
return false;
241+
}
242+
}, creationalContext);
243+
244+
final String claimName = getClaimName(injectionPoint);
245+
if (claimName != null) {
246+
final Object claim = jsonWebToken.getClaim(claimName);
247+
if (claim == null) {
248+
return null;
249+
}
250+
return (T) converters.convertValue(claim.toString(), klass);
251+
}
252+
253+
return null;
254+
}
255+
256+
@Override
257+
public void destroy(final T instance, final CreationalContext<T> creationalContext) {
258+
259+
}
260+
261+
@Override
262+
public Set<Type> getTypes() {
263+
return Collections.singleton(klass);
264+
}
265+
266+
@Override
267+
public Set<Annotation> getQualifiers() {
268+
return Collections.singleton(ClaimLiteral.INSTANCE);
269+
}
270+
271+
@Override
272+
public Class<? extends Annotation> getScope() {
273+
return Dependent.class;
274+
}
275+
276+
@Override
277+
public String getName() {
278+
return this.getClass().getName() + "_" + klass;
279+
}
280+
281+
@Override
282+
public Set<Class<? extends Annotation>> getStereotypes() {
283+
return Collections.emptySet();
284+
}
285+
286+
@Override
287+
public boolean isAlternative() {
288+
return false;
289+
}
290+
291+
@Override
292+
public String getId() {
293+
return getName();
294+
}
295+
296+
private static String getClaimName(InjectionPoint ip) {
297+
String name = null;
298+
for (Annotation ann : ip.getQualifiers()) {
299+
if (ann instanceof Claim) {
300+
Claim claim = (Claim) ann;
301+
name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
302+
}
303+
}
304+
return name;
305+
}
306+
}
307+
308+
private static final class ClaimLiteral extends AnnotationLiteral<Claim> implements Claim {
309+
310+
public static final ClaimLiteral INSTANCE = new ClaimLiteral();
311+
312+
private static final long serialVersionUID = 1L;
313+
314+
@Override
315+
public String value() {
316+
return INSTANCE.value();
317+
}
318+
319+
@Override
320+
public Claims standard() {
321+
return INSTANCE.standard();
322+
}
323+
}
324+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"iss": "https://server.example.com",
3+
"jti": "a-123",
4+
"sub": "24400320",
5+
"upn": "jdoe@example.com",
6+
"preferred_username": "jdoe",
7+
"aud": "s6BhdRkqt3",
8+
"exp": 1311281970,
9+
"iat": 1311280970,
10+
"auth_time": 1311280969,
11+
12+
"byte": 1,
13+
"short": 9,
14+
"integer": 99,
15+
"long": 999,
16+
"float": 99.9,
17+
"double": 99.99,
18+
"boolean": "true",
19+
"char": "y"
20+
}

0 commit comments

Comments
 (0)