Skip to content

Commit 18a01a9

Browse files
committed
Support custom claim types in quarkus-test-security-jwt
1 parent 94c34c2 commit 18a01a9

File tree

5 files changed

+251
-64
lines changed

5 files changed

+251
-64
lines changed

test-framework/security-jwt/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
<artifactId>junit-jupiter</artifactId>
2929
<scope>compile</scope>
3030
</dependency>
31+
<dependency>
32+
<groupId>io.quarkus</groupId>
33+
<artifactId>quarkus-jsonp</artifactId>
34+
</dependency>
3135
<dependency>
3236
<groupId>org.eclipse.microprofile.jwt</groupId>
3337
<artifactId>microprofile-jwt-auth-api</artifactId>

test-framework/security-jwt/src/main/java/io/quarkus/test/security/jwt/Claim.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,19 @@
77
@Retention(RetentionPolicy.RUNTIME)
88
@Target({})
99
public @interface Claim {
10+
/**
11+
* Claim name
12+
*/
1013
String key();
1114

15+
/**
16+
* Claim value
17+
*/
1218
String value();
19+
20+
/**
21+
* Claim value type, the value will be converted to String if this type is set to Object.class.
22+
* Supported types: String, Integer, int, Long, long, Boolean, boolean, jakarta.json.JsonArray, jakarta.json.JsonObject.
23+
*/
24+
Class<?> type() default Object.class;
1325
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package io.quarkus.test.security.jwt;
2+
3+
import java.io.StringReader;
4+
import java.lang.annotation.Annotation;
5+
import java.util.Arrays;
6+
import java.util.Collections;
7+
import java.util.Map;
8+
import java.util.Set;
9+
import java.util.stream.Collectors;
10+
11+
import jakarta.json.Json;
12+
import jakarta.json.JsonArray;
13+
import jakarta.json.JsonObject;
14+
import jakarta.json.JsonReader;
15+
16+
import org.eclipse.microprofile.jwt.Claims;
17+
import org.eclipse.microprofile.jwt.JsonWebToken;
18+
19+
import io.quarkus.security.identity.SecurityIdentity;
20+
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
21+
import io.quarkus.test.security.TestSecurityIdentityAugmentor;
22+
23+
public class JwtTestSecurityIdentityAugmentor implements TestSecurityIdentityAugmentor {
24+
private static Converter<Long> longConverter = new LongConverter();
25+
private static Converter<Integer> intConverter = new IntegerConverter();
26+
private static Converter<Boolean> booleanConverter = new BooleanConverter();
27+
private static Map<String, Converter<?>> standardClaimConverteres = Map.of(
28+
Claims.exp.name(), longConverter,
29+
Claims.iat.name(), longConverter,
30+
Claims.nbf.name(), longConverter,
31+
Claims.auth_time.name(), longConverter,
32+
Claims.email_verified.name(), booleanConverter);
33+
34+
private static Map<Class<?>, Converter<?>> converters = Map.of(
35+
String.class, new StringConverter(),
36+
Integer.class, intConverter,
37+
int.class, intConverter,
38+
Long.class, longConverter,
39+
long.class, longConverter,
40+
Boolean.class, booleanConverter,
41+
boolean.class, booleanConverter,
42+
JsonArray.class, new JsonArrayConverter(),
43+
JsonObject.class, new JsonObjectConverter());
44+
45+
@Override
46+
public SecurityIdentity augment(final SecurityIdentity identity, final Annotation[] annotations) {
47+
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
48+
49+
final JwtSecurity jwtSecurity = findJwtSecurity(annotations);
50+
builder.setPrincipal(new JsonWebToken() {
51+
52+
@Override
53+
public String getName() {
54+
return identity.getPrincipal().getName();
55+
}
56+
57+
@SuppressWarnings("unchecked")
58+
@Override
59+
public <T> T getClaim(String claimName) {
60+
if (Claims.groups.name().equals(claimName)) {
61+
return (T) identity.getRoles();
62+
}
63+
if (jwtSecurity != null && jwtSecurity.claims() != null) {
64+
for (Claim claim : jwtSecurity.claims()) {
65+
if (claim.key().equals(claimName)) {
66+
return convertClaimValue(claim);
67+
}
68+
}
69+
}
70+
return null;
71+
}
72+
73+
@Override
74+
public Set<String> getClaimNames() {
75+
if (jwtSecurity != null && jwtSecurity.claims() != null) {
76+
return Arrays.stream(jwtSecurity.claims()).map(Claim::key).collect(Collectors.toSet());
77+
}
78+
return Collections.emptySet();
79+
}
80+
81+
});
82+
83+
return builder.build();
84+
}
85+
86+
private static JwtSecurity findJwtSecurity(Annotation[] annotations) {
87+
for (Annotation ann : annotations) {
88+
if (ann instanceof JwtSecurity) {
89+
return (JwtSecurity) ann;
90+
}
91+
}
92+
return null;
93+
}
94+
95+
@SuppressWarnings("unchecked")
96+
private <T> T convertClaimValue(Claim claim) {
97+
if (claim.type() != Object.class) {
98+
Converter<?> converter = converters.get(claim.type());
99+
if (converter != null) {
100+
return (T) converter.convert(claim.value());
101+
} else {
102+
throw new RuntimeException("Unsupported claim type: " + claim.type().getName());
103+
}
104+
} else if (standardClaimConverteres.containsKey(claim.key())) {
105+
Converter<?> converter = standardClaimConverteres.get(claim.key());
106+
return (T) converter.convert(claim.value());
107+
} else {
108+
return (T) claim.value();
109+
}
110+
}
111+
112+
private static interface Converter<T> {
113+
T convert(String value);
114+
}
115+
116+
private static class StringConverter implements Converter<String> {
117+
@Override
118+
public String convert(String value) {
119+
return value;
120+
}
121+
}
122+
123+
private static class IntegerConverter implements Converter<Integer> {
124+
@Override
125+
public Integer convert(String value) {
126+
return Integer.valueOf(value);
127+
}
128+
}
129+
130+
private static class LongConverter implements Converter<Long> {
131+
@Override
132+
public Long convert(String value) {
133+
return Long.valueOf(value);
134+
}
135+
}
136+
137+
private static class BooleanConverter implements Converter<Boolean> {
138+
@Override
139+
public Boolean convert(String value) {
140+
return Boolean.valueOf(value);
141+
}
142+
}
143+
144+
private static class JsonObjectConverter implements Converter<JsonObject> {
145+
@Override
146+
public JsonObject convert(String value) {
147+
try (JsonReader jsonReader = Json.createReader(new StringReader(value))) {
148+
return jsonReader.readObject();
149+
}
150+
}
151+
}
152+
153+
private static class JsonArrayConverter implements Converter<JsonArray> {
154+
@Override
155+
public JsonArray convert(String value) {
156+
try (JsonReader jsonReader = Json.createReader(new StringReader(value))) {
157+
return jsonReader.readArray();
158+
}
159+
}
160+
}
161+
}
Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
package io.quarkus.test.security.jwt;
22

3-
import java.lang.annotation.Annotation;
4-
import java.util.Arrays;
5-
import java.util.Collections;
6-
import java.util.Set;
7-
import java.util.stream.Collectors;
8-
93
import jakarta.enterprise.context.ApplicationScoped;
104
import jakarta.enterprise.inject.Produces;
115

12-
import org.eclipse.microprofile.jwt.Claims;
13-
import org.eclipse.microprofile.jwt.JsonWebToken;
14-
156
import io.quarkus.arc.Unremovable;
16-
import io.quarkus.security.identity.SecurityIdentity;
17-
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
187
import io.quarkus.test.security.TestSecurityIdentityAugmentor;
198

209
@ApplicationScoped
@@ -25,57 +14,4 @@ public class JwtTestSecurityIdentityAugmentorProducer {
2514
public TestSecurityIdentityAugmentor produce() {
2615
return new JwtTestSecurityIdentityAugmentor();
2716
}
28-
29-
private static class JwtTestSecurityIdentityAugmentor implements TestSecurityIdentityAugmentor {
30-
31-
@Override
32-
public SecurityIdentity augment(final SecurityIdentity identity, final Annotation[] annotations) {
33-
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
34-
35-
final JwtSecurity jwtSecurity = findJwtSecurity(annotations);
36-
builder.setPrincipal(new JsonWebToken() {
37-
38-
@Override
39-
public String getName() {
40-
return identity.getPrincipal().getName();
41-
}
42-
43-
@SuppressWarnings("unchecked")
44-
@Override
45-
public <T> T getClaim(String claimName) {
46-
if (Claims.groups.name().equals(claimName)) {
47-
return (T) identity.getRoles();
48-
}
49-
if (jwtSecurity != null && jwtSecurity.claims() != null) {
50-
for (Claim claim : jwtSecurity.claims()) {
51-
if (claim.key().equals(claimName)) {
52-
return (T) claim.value();
53-
}
54-
}
55-
}
56-
return null;
57-
}
58-
59-
@Override
60-
public Set<String> getClaimNames() {
61-
if (jwtSecurity != null && jwtSecurity.claims() != null) {
62-
return Arrays.stream(jwtSecurity.claims()).map(Claim::key).collect(Collectors.toSet());
63-
}
64-
return Collections.emptySet();
65-
}
66-
67-
});
68-
69-
return builder.build();
70-
}
71-
72-
private JwtSecurity findJwtSecurity(Annotation[] annotations) {
73-
for (Annotation ann : annotations) {
74-
if (ann instanceof JwtSecurity) {
75-
return (JwtSecurity) ann;
76-
}
77-
}
78-
return null;
79-
}
80-
}
8117
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.quarkus.test.security.jwt;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.lang.annotation.Annotation;
8+
import java.security.Principal;
9+
import java.util.Set;
10+
11+
import jakarta.json.JsonArray;
12+
import jakarta.json.JsonObject;
13+
14+
import org.eclipse.microprofile.jwt.Claims;
15+
import org.eclipse.microprofile.jwt.JsonWebToken;
16+
import org.junit.jupiter.api.Test;
17+
18+
import io.quarkus.security.identity.SecurityIdentity;
19+
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
20+
21+
public class JwtTestSecurityIdentityAugmentorTest {
22+
23+
@Test
24+
@JwtSecurity(claims = {
25+
@Claim(key = "exp", value = "123456789"),
26+
@Claim(key = "iat", value = "123456788"),
27+
@Claim(key = "nbf", value = "123456787"),
28+
@Claim(key = "auth_time", value = "123456786"),
29+
@Claim(key = "customlong", value = "123456785", type = Long.class),
30+
@Claim(key = "email", value = "[email protected]"),
31+
@Claim(key = "email_verified", value = "true"),
32+
@Claim(key = "email_checked", value = "false", type = Boolean.class),
33+
@Claim(key = "jsonarray_claim", value = "[\"1\", \"2\"]", type = JsonArray.class),
34+
@Claim(key = "jsonobject_claim", value = "{\"a\":\"1\", \"b\":\"2\"}", type = JsonObject.class)
35+
})
36+
public void testClaimValues() throws Exception {
37+
SecurityIdentity identity = QuarkusSecurityIdentity.builder()
38+
.setPrincipal(new Principal() {
39+
@Override
40+
public String getName() {
41+
return "alice";
42+
}
43+
44+
})
45+
.addRole("user")
46+
.build();
47+
48+
JwtTestSecurityIdentityAugmentor augmentor = new JwtTestSecurityIdentityAugmentor();
49+
50+
Annotation[] annotations = JwtTestSecurityIdentityAugmentorTest.class.getMethod("testClaimValues").getAnnotations();
51+
JsonWebToken jwt = (JsonWebToken) augmentor.augment(identity, annotations).getPrincipal();
52+
53+
assertEquals("alice", jwt.getName());
54+
assertEquals(Set.of("user"), jwt.getGroups());
55+
56+
assertEquals(123456789, jwt.getExpirationTime());
57+
assertEquals(123456788, jwt.getIssuedAtTime());
58+
assertEquals(123456787, (Long) jwt.getClaim(Claims.nbf.name()));
59+
assertEquals(123456786, (Long) jwt.getClaim(Claims.auth_time.name()));
60+
assertEquals(123456785, (Long) jwt.getClaim("customlong"));
61+
assertEquals("[email protected]", jwt.getClaim(Claims.email));
62+
assertTrue((Boolean) jwt.getClaim(Claims.email_verified.name()));
63+
assertFalse((Boolean) jwt.getClaim("email_checked"));
64+
65+
JsonArray array = jwt.getClaim("jsonarray_claim");
66+
assertEquals("1", array.getString(0));
67+
assertEquals("2", array.getString(1));
68+
69+
JsonObject map = jwt.getClaim("jsonobject_claim");
70+
assertEquals("1", map.getString("a"));
71+
assertEquals("2", map.getString("b"));
72+
}
73+
74+
}

0 commit comments

Comments
 (0)