Ether Framework
Unified API docs for Ether modules
Loading...
Searching...
No Matches
ClaimsMapper.java
Go to the documentation of this file.
1package dev.rafex.ether.jwt.internal;
2
3/*-
4 * #%L
5 * ether-jwt
6 * %%
7 * Copyright (C) 2025 - 2026 Raúl Eduardo González Argote
8 * %%
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 * #L%
27 */
28
29import com.fasterxml.jackson.core.type.TypeReference;
30import com.fasterxml.jackson.databind.JsonNode;
31import com.fasterxml.jackson.databind.ObjectMapper;
32import com.fasterxml.jackson.databind.node.ArrayNode;
33import com.fasterxml.jackson.databind.node.ObjectNode;
34import dev.rafex.ether.jwt.TokenClaims;
35import dev.rafex.ether.jwt.TokenType;
36
37import java.time.Instant;
38import java.util.ArrayList;
39import java.util.LinkedHashMap;
40import java.util.List;
41import java.util.Map;
42
43public final class ClaimsMapper {
44
45 private static final ObjectMapper MAPPER = new ObjectMapper();
46
47 private ClaimsMapper() {
48 }
49
50 public static ObjectNode toPayload(final TokenClaims claims) {
51 final ObjectNode node = MAPPER.createObjectNode();
52
53 putString(node, "sub", claims.subject());
54 putString(node, "iss", claims.issuer());
55 putArray(node, "aud", claims.audience());
56 putInstant(node, "iat", claims.issuedAt());
57 putInstant(node, "exp", claims.expiresAt());
58 putInstant(node, "nbf", claims.notBefore());
59 putString(node, "jti", claims.jwtId());
60
61 putArray(node, "roles", claims.roles());
62 if (claims.tokenType() != null) {
63 node.put("token_type", claims.tokenType().claimValue());
64 }
65 putString(node, "client_id", claims.clientId());
66
67 for (final Map.Entry<String, Object> entry : claims.extras().entrySet()) {
68 node.set(entry.getKey(), MAPPER.valueToTree(entry.getValue()));
69 }
70 return node;
71 }
72
73 public static TokenClaims fromPayload(final JsonNode payload) {
74 final Map<String, Object> allClaims = MAPPER.convertValue(payload, new TypeReference<>() {
75 });
76 final Map<String, Object> extras = new LinkedHashMap<>(allClaims);
77 extras.keySet().removeIf(ClaimsMapper::isKnownClaim);
78
79 final String tokenTypeRaw = asText(payload.get("token_type"));
80 return TokenClaims.builder()
81 .subject(asText(payload.get("sub")))
82 .issuer(asText(payload.get("iss")))
83 .audience(readStringList(payload.get("aud")))
84 .issuedAt(asInstant(payload.get("iat")))
85 .expiresAt(asInstant(payload.get("exp")))
86 .notBefore(asInstant(payload.get("nbf")))
87 .jwtId(asText(payload.get("jti")))
88 .roles(readStringList(payload.get("roles")))
89 .tokenType(tokenTypeRaw == null ? null : TokenType.fromClaimValue(tokenTypeRaw))
90 .clientId(asText(payload.get("client_id")))
91 .extras(extras)
92 .build();
93 }
94
95 public static String tokenTypeRaw(final JsonNode payload) {
96 return asText(payload.get("token_type"));
97 }
98
99 private static boolean isKnownClaim(final String claim) {
100 return "sub".equals(claim)
101 || "iss".equals(claim)
102 || "aud".equals(claim)
103 || "exp".equals(claim)
104 || "iat".equals(claim)
105 || "nbf".equals(claim)
106 || "jti".equals(claim)
107 || "roles".equals(claim)
108 || "token_type".equals(claim)
109 || "client_id".equals(claim);
110 }
111
112 private static void putString(final ObjectNode node, final String name, final String value) {
113 if (value != null && !value.isBlank()) {
114 node.put(name, value);
115 }
116 }
117
118 private static void putInstant(final ObjectNode node, final String name, final Instant value) {
119 if (value != null) {
120 node.put(name, value.getEpochSecond());
121 }
122 }
123
124 private static void putArray(final ObjectNode node, final String name, final List<String> values) {
125 if (values == null || values.isEmpty()) {
126 return;
127 }
128 final ArrayNode arrayNode = node.putArray(name);
129 for (final String value : values) {
130 if (value != null && !value.isBlank()) {
131 arrayNode.add(value);
132 }
133 }
134 }
135
136 private static String asText(final JsonNode node) {
137 if (node == null || node.isNull()) {
138 return null;
139 }
140 final String text = node.asText();
141 return text == null || text.isBlank() ? null : text;
142 }
143
144 private static Instant asInstant(final JsonNode node) {
145 if (node == null || node.isNull()) {
146 return null;
147 }
148 if (!node.isIntegralNumber()) {
149 throw new IllegalArgumentException("time claim must be epoch seconds");
150 }
151 return Instant.ofEpochSecond(node.asLong());
152 }
153
154 private static List<String> readStringList(final JsonNode node) {
155 if (node == null || node.isNull()) {
156 return List.of();
157 }
158 if (node.isArray()) {
159 final List<String> values = new ArrayList<>();
160 for (final JsonNode element : node) {
161 final String text = asText(element);
162 if (text != null) {
163 values.add(text);
164 }
165 }
166 return values;
167 }
168 final String singleValue = asText(node);
169 return singleValue == null ? List.of() : List.of(singleValue);
170 }
171}
Normalized claims extracted from a JWT token.
static String tokenTypeRaw(final JsonNode payload)
static TokenClaims fromPayload(final JsonNode payload)
static ObjectNode toPayload(final TokenClaims claims)
Supported business token types.
static TokenType fromClaimValue(final String claimValue)