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