Skip to content

Commit 4a4564f

Browse files
Thomasludomikula
Thomas
authored andcommitted
add JWT decoder
merge user info from jwt and user endpoint
1 parent 8c7ea98 commit 4a4564f

File tree

5 files changed

+132
-4
lines changed

5 files changed

+132
-4
lines changed

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/request/GenericAuthRequest.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.lowcoder.api.authentication.request.AuthException;
44
import org.lowcoder.api.authentication.request.oauth2.GenericOAuthProviderSource;
55
import org.lowcoder.api.authentication.request.oauth2.OAuth2RequestContext;
6+
import org.lowcoder.api.authentication.util.JwtDecoderUtil;
67
import org.lowcoder.domain.user.model.AuthToken;
78
import org.lowcoder.domain.user.model.AuthUser;
89
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
@@ -14,8 +15,7 @@
1415

1516
import java.util.Map;
1617

17-
import static org.lowcoder.api.authentication.util.AuthenticationUtils.mapToAuthToken;
18-
import static org.lowcoder.api.authentication.util.AuthenticationUtils.mapToAuthUser;
18+
import static org.lowcoder.api.authentication.util.AuthenticationUtils.*;
1919
import static org.lowcoder.sdk.plugin.common.constant.Constants.HTTP_TIMEOUT;
2020

2121
/**
@@ -75,7 +75,22 @@ protected Mono<AuthToken> refreshAuthToken(String refreshToken) {
7575

7676
@Override
7777
protected Mono<AuthUser> getAuthUser(AuthToken authToken) {
78-
if(!Boolean.TRUE.equals(config.getUserInfoIntrospection())) return Mono.just(AuthUser.builder().build());
78+
//parse the JWT token
79+
String jwt = authToken.getJwt();
80+
Map<String, Object> jwtMap = null;
81+
if(jwt != null) {
82+
try {
83+
jwtMap = JwtDecoderUtil.decodeJwtPayload(jwt);
84+
} catch (Exception ignored) {
85+
}
86+
}
87+
88+
if(!Boolean.TRUE.equals(config.getUserInfoIntrospection())) {
89+
if(jwtMap == null) return Mono.error(new AuthException("No JWT token found"));
90+
return Mono.just(mapToAuthUser(jwtMap, config.getSourceMappings()));
91+
}
92+
93+
Map<String, Object> finalJwtMap = jwtMap;
7994
return WebClientBuildHelper.builder()
8095
.systemProxy()
8196
.timeoutMs(HTTP_TIMEOUT)
@@ -89,7 +104,8 @@ protected Mono<AuthUser> getAuthUser(AuthToken authToken) {
89104
if (map.containsKey("error") || map.containsKey("error_description")) {
90105
return Mono.error(new AuthException(JsonUtils.toJson(map)));
91106
}
92-
return Mono.just(mapToAuthUser(map, config.getSourceMappings()));
107+
AuthUser merged = mergeAuthUser(mapToAuthUser(finalJwtMap, config.getSourceMappings()), mapToAuthUser(map, config.getSourceMappings()));
108+
return Mono.just(merged);
93109
});
94110
}
95111
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/util/AdvancedMapUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class AdvancedMapUtils {
1212
* @return The string value if found, otherwise null.
1313
*/
1414
public static String getString(Map<String, Object> map, String key) {
15+
if(key == null) return null;
1516
String[] parts = key.split("\\.");
1617
Object current = map;
1718

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/util/AuthenticationUtils.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,20 @@ public static AuthUser mapToAuthUser(Map<String, Object> map, HashMap<String, St
9393
.rawUserInfo(map)
9494
.build();
9595
}
96+
97+
/**
98+
* Merge two AuthUser object - overwrite high into low
99+
* @param low base object for merge
100+
* @param high overwriting object
101+
* @return
102+
*/
103+
public static AuthUser mergeAuthUser(AuthUser low, AuthUser high) {
104+
return AuthUser.builder()
105+
.uid(high.getUid() != null ? high.getUid() : low.getUid())
106+
.username(high.getUsername() != null ? high.getUsername() : low.getUsername())
107+
.avatar(high.getAvatar() != null ? high.getAvatar() : low.getAvatar())
108+
.rawUserInfo(high.getRawUserInfo() != null ? high.getRawUserInfo() : low.getRawUserInfo())
109+
.authToken(high.getAuthToken() != null ? high.getAuthToken() : low.getAuthToken())
110+
.build();
111+
}
96112
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.lowcoder.api.authentication.util;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import java.util.Base64;
5+
import java.util.Map;
6+
7+
public class JwtDecoderUtil {
8+
9+
private static final ObjectMapper objectMapper = new ObjectMapper();
10+
11+
/**
12+
* Decodes the payload of a JWT without verifying its signature.
13+
*
14+
* @param jwt The JWT string.
15+
* @return A Map representing the decoded JWT payload.
16+
* @throws Exception if there is an error decoding the JWT.
17+
*/
18+
public static Map<String, Object> decodeJwtPayload(String jwt) throws Exception {
19+
// Split the JWT into its components
20+
String[] parts = jwt.split("\\.");
21+
if (parts.length < 2) {
22+
throw new IllegalArgumentException("Invalid JWT format.");
23+
}
24+
25+
// Base64-decode the payload
26+
String payload = parts[1];
27+
byte[] decodedBytes = Base64.getUrlDecoder().decode(payload);
28+
29+
// Convert the decoded bytes to a JSON string
30+
String decodedString = new String(decodedBytes);
31+
32+
// Convert the JSON string to a Map
33+
return objectMapper.readValue(decodedString, Map.class);
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.lowcoder.api.authentication;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.lowcoder.api.authentication.util.JwtDecoderUtil;
5+
6+
import java.util.Map;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
class JwtDecoderUtilTest {
11+
12+
@Test
13+
void testDecodeJwtPayload_ValidJwt() throws Exception {
14+
// Example JWT with payload: {"sub":"1234567890","name":"John Doe","iat":1516239022}
15+
String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
16+
17+
Map<String, Object> payload = JwtDecoderUtil.decodeJwtPayload(jwt);
18+
19+
assertNotNull(payload);
20+
assertEquals("1234567890", payload.get("sub"));
21+
assertEquals("John Doe", payload.get("name"));
22+
assertEquals(1516239022, payload.get("iat"));
23+
}
24+
25+
@Test
26+
void testDecodeJwtPayload_InvalidJwtFormat() {
27+
// Example of an invalid JWT (missing parts)
28+
String jwt = "invalid-jwt";
29+
30+
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
31+
JwtDecoderUtil.decodeJwtPayload(jwt);
32+
});
33+
34+
assertEquals("Invalid JWT format.", exception.getMessage());
35+
}
36+
37+
@Test
38+
void testDecodeJwtPayload_InvalidBase64() {
39+
// Example of a JWT with an invalid base64 payload part
40+
String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.invalid-base64.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
41+
42+
Exception exception = assertThrows(Exception.class, () -> {
43+
JwtDecoderUtil.decodeJwtPayload(jwt);
44+
});
45+
46+
assertTrue(exception instanceof IllegalArgumentException || exception instanceof java.io.IOException);
47+
}
48+
49+
@Test
50+
void testDecodeJwtPayload_EmptyPayload() throws Exception {
51+
// Example of a JWT with an empty payload
52+
String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
53+
54+
Exception exception = assertThrows(Exception.class, () -> {
55+
JwtDecoderUtil.decodeJwtPayload(jwt);
56+
});
57+
58+
assertTrue(exception instanceof java.io.IOException);
59+
}
60+
}

0 commit comments

Comments
 (0)