Skip to content

Commit fcd5f98

Browse files
Feature2.4.17 (#289)
* backward compatible with JWT * backward compatible with JWT and configurable in etc/boot.ini * backward compatible with JWT and configurable in etc/boot.ini * refactoing * refactoing * release2.4.17
1 parent 57569b1 commit fcd5f98

File tree

8 files changed

+262
-161
lines changed

8 files changed

+262
-161
lines changed

pom.xml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>org.summerboot</groupId>
66
<artifactId>jexpress</artifactId>
7-
<version>2.4.16</version>
7+
<version>2.4.17</version>
88
<packaging>jar</packaging>
99
<name>Summer Boot jExpress</name>
1010
<description>Summer Boot jExpress focuses on solving non-functional and operational maintainability requirements,
@@ -190,56 +190,56 @@
190190
<!-- Commons -->
191191
<commons-lang3.version>3.17.0</commons-lang3.version>
192192
<commons-cli.version>1.9.0</commons-cli.version>
193-
<commons-io.version>2.17.0</commons-io.version>
193+
<commons-io.version>2.18.0</commons-io.version>
194194
<!-- <commons-text.version>1.11.0</commons-text.version>-->
195195
<!-- <owasp.encoder.version>1.2.3</owasp.encoder.version>-->
196196
<!-- Logging -->
197-
<log4j-api.version>2.24.1</log4j-api.version>
197+
<log4j-api.version>2.24.2</log4j-api.version>
198198
<log4j-disruptor.version>4.0.0</log4j-disruptor.version>
199199
<!-- Mail -->
200200
<jakarta-mail.version>2.0.1</jakarta-mail.version>
201201
<!-- Security -->
202-
<bouncycastle.version>1.78.1</bouncycastle.version>
202+
<bouncycastle.version>1.79</bouncycastle.version>
203203
<!-- JWT -->
204204
<jwt.version>0.12.6</jwt.version>
205205

206206
<!-- NIO Netty -->
207-
<netty.version>4.1.114.Final</netty.version>
208-
<netty-tcnative.version>2.0.66.Final</netty-tcnative.version>
207+
<netty.version>4.1.115.Final</netty.version>
208+
<netty-tcnative.version>2.0.69.Final</netty-tcnative.version>
209209
<!-- gRPC and protobuf -->
210-
<grpc.version>1.68.0</grpc.version>
210+
<grpc.version>1.68.1</grpc.version>
211211
<guava.version>33.3.1-jre</guava.version>
212-
<protobuf.version>4.28.2</protobuf.version>
212+
<protobuf.version>4.28.3</protobuf.version>
213213
<!-- Web JAX-RS -->
214-
<swagger.core.version>2.2.23</swagger.core.version>
214+
<swagger.core.version>2.2.26</swagger.core.version>
215215
<!--<elastic-apm.version>1.36.0</elastic-apm.version>-->
216216

217217

218218
<!-- MIME-Type -->
219-
<tika.version>2.9.2</tika.version>
219+
<tika.version>3.0.0</tika.version>
220220
<!-- JAX-RS -->
221221
<rs-api.version>4.0.0</rs-api.version>
222222
<jakarta.annotation.version>3.0.0</jakarta.annotation.version>
223223
<reflections.version>0.10.2</reflections.version>
224224

225225
<!-- JSON/XML/Bean Validation -->
226-
<jackson.version>2.18.0</jackson.version>
226+
<jackson.version>2.18.1</jackson.version>
227227
<!-- Bean Validation -->
228228
<jakarta.el.version>6.0.1</jakarta.el.version>
229-
<tomcat-embed-el.version>11.0.0-M24</tomcat-embed-el.version>
229+
<tomcat-embed-el.version>11.0.1</tomcat-embed-el.version>
230230
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
231231

232232
<!-- IOC Injection -->
233233
<guice.version>7.0.0</guice.version>
234234

235235
<!-- JPA -->
236-
<hibernate.version>6.6.1.Final</hibernate.version>
237-
<hikari-cp.version>6.0.0</hikari-cp.version>
236+
<hibernate.version>6.6.3.Final</hibernate.version>
237+
<hikari-cp.version>6.2.1</hikari-cp.version>
238238

239239
<!-- Cache -->
240240
<jedis.version>5.2.0</jedis.version>
241241

242-
<quartz.version>2.5.0-rc1</quartz.version>
242+
<quartz.version>2.5.0</quartz.version>
243243
<mqtt.version>1.2.5</mqtt.version>
244244

245245
<!-- Template Engine -->
@@ -252,12 +252,12 @@
252252
<openhtml.version>1.0.10</openhtml.version>
253253
<batik-transcoder.version>1.18</batik-transcoder.version>
254254
<!-- PDF - iText -->
255-
<itext7-core.version>8.0.5</itext7-core.version>
256-
<itext7-html2pdf.version>5.0.5</itext7-html2pdf.version>
255+
<itext7-core.version>9.0.0</itext7-core.version>
256+
<itext7-html2pdf.version>6.0.0</itext7-html2pdf.version>
257257

258258
<!-- Testing -->
259259
<testng.version>7.10.2</testng.version>
260-
<jdbc.version>9.0.0</jdbc.version>
260+
<jdbc.version>9.1.0</jdbc.version>
261261
</properties>
262262

263263
<dependencies>

src/main/java/org/summerboot/jexpress/boot/BackOffice.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ public Map<Integer, Integer> getBootErrorCodeMapping() {
161161
@Config(key = "type.errorCodeAsInt", defaultValue = "false")
162162
private boolean errorCodeAsInt = false;
163163

164+
@Config(key = "type.JWTAudAsCSV", defaultValue = "true", desc = "Parse JWT Audience value as CSV for JWT backward compatibility")
165+
private boolean jwtAudAsCSV = true;
166+
164167
@Config(key = "default.interval.ConfigChangeMonitor", defaultValue = "30")
165168
private int CfgChangeMonitorIntervalSec = 30;
166169

@@ -320,6 +323,10 @@ public boolean isErrorCodeAsInt() {
320323
return errorCodeAsInt;
321324
}
322325

326+
public boolean isJwtAudAsCSV() {
327+
return jwtAudAsCSV;
328+
}
329+
323330
public int getCfgChangeMonitorIntervalSec() {
324331
return CfgChangeMonitorIntervalSec;
325332
}

src/main/java/org/summerboot/jexpress/boot/BootConstant.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public interface BootConstant {
2727
String APP_ID = String.format("%06d", new Random().nextInt(999999));
2828

2929
//version
30-
String VERSION = "jExpress 2.4.16";
30+
String VERSION = "jExpress 2.4.17";
3131
String JEXPRESS_PACKAGE_NAME = "org.summerboot.jexpress";
3232

3333
String DEFAULT_ADMIN_MM = "changeit";
@@ -48,6 +48,7 @@ public interface BootConstant {
4848
* 3. jExpress Default Settings
4949
*/
5050
boolean CFG_ERROR_CODE_AS_INT = BackOffice.agent.isErrorCodeAsInt();
51+
boolean CFG_JWT_AUD_AS_CSV = BackOffice.agent.isJwtAudAsCSV();
5152
int CFG_CHANGE_MONITOR_INTERVAL_SEC = BackOffice.agent.getCfgChangeMonitorIntervalSec();
5253
int PACKAGE_LEVEL = BackOffice.agent.getReflectionPackageLevel();
5354
long WEB_RESOURCE_TTL_MS = BackOffice.agent.getWebResourceCacheTtlSec() * 1000;

src/main/java/org/summerboot/jexpress/nio/server/ws/rs/BootController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
abstract public class BootController extends PingController {
9797

9898
public static final String TAG_APP_ADMIN = "App Admin";
99-
public static final String TAG_USER_AUTH = "App User Authentication";
99+
public static final String TAG_USER_AUTH = "App Authentication";
100100

101101

102102
public static final String DESC_400 = "All other 4xx code. The client cannot continue and should not re-try again with the request without modification.";

src/main/java/org/summerboot/jexpress/security/auth/Authenticator.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,26 @@ public interface Authenticator<T> {
5353
*/
5454
String signJWT(String username, String pwd, T metaData, int validForMinutes, final ServiceContext context) throws NamingException;
5555

56+
57+
/**
58+
* Success HTTP Status: 201 Created
59+
*
60+
* @param caller
61+
* @param validForMinutes
62+
* @param context
63+
* @return
64+
*/
65+
String signJWT(Caller caller, int validForMinutes, final ServiceContext context);
66+
5667
/**
5768
* Convert Caller to auth token, override this method to implement
5869
* customized token format
5970
*
6071
* @param caller
72+
* @param txId
6173
* @return formatted auth token builder
6274
*/
63-
JwtBuilder toJwt(Caller caller);
75+
JwtBuilder toJwt(Caller caller, String txId);
6476

6577
/**
6678
* Success HTTP Status: 200 OK

src/main/java/org/summerboot/jexpress/security/auth/BootAuthenticator.java

Lines changed: 78 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import io.netty.handler.codec.http.HttpHeaders;
3535
import io.netty.handler.codec.http.HttpResponseStatus;
3636
import org.apache.commons.lang3.StringUtils;
37+
import org.summerboot.jexpress.boot.BootConstant;
3738
import org.summerboot.jexpress.boot.BootErrorCode;
3839
import org.summerboot.jexpress.boot.BootPOI;
3940
import org.summerboot.jexpress.integration.cache.AuthTokenCache;
@@ -42,11 +43,13 @@
4243
import org.summerboot.jexpress.nio.server.domain.Err;
4344
import org.summerboot.jexpress.nio.server.domain.ServiceContext;
4445
import org.summerboot.jexpress.security.JwtUtil;
46+
import org.summerboot.jexpress.util.FormatterUtil;
4547

4648
import javax.naming.NamingException;
4749
import java.security.Key;
4850
import java.time.Duration;
4951
import java.util.Date;
52+
import java.util.Map;
5053
import java.util.Set;
5154

5255
/**
@@ -81,28 +84,27 @@ public String signJWT(String username, String pwd, E metaData, int validForMinut
8184
context.poi(BootPOI.LDAP_BEGIN);
8285
Caller caller = authenticate(username, pwd, (E) metaData, authenticatorListener, context);
8386
context.poi(BootPOI.LDAP_END);
87+
88+
return signJWT(caller, validForMinutes, context);
89+
}
90+
91+
@Override
92+
public String signJWT(Caller caller, int validForMinutes, final ServiceContext context) {
8493
if (caller == null) {
8594
context.status(HttpResponseStatus.UNAUTHORIZED);
8695
return null;
8796
}
8897

89-
// get token TTL from caller, otherwise use default
90-
Long tokenTtlSec = caller.getTokenTtlSec();
91-
Duration tokenTTL;
92-
if (tokenTtlSec != null) {
93-
tokenTTL = Duration.ofSeconds(tokenTtlSec);
94-
} else {
95-
tokenTTL = Duration.ofMinutes(validForMinutes);
96-
}
97-
98-
//3. format JWT
99-
JwtBuilder builder = toJwt(caller);
98+
//3. format JWT and set token TTL from caller
99+
JwtBuilder builder = toJwt(caller, context.txId());
100100

101-
//4. create JWT
101+
//5. create JWT
102102
Key signingKey = AuthConfig.cfg.getJwtSigningKey();
103103
if (signingKey == null) {
104104
throw new UnsupportedOperationException(ERROR_NO_CFG);
105105
}
106+
// override caller TTL if validForMinutes is greater than 0
107+
Duration tokenTTL = Duration.ofMinutes(validForMinutes);
106108
String token = JwtUtil.createJWT(signingKey, builder, tokenTTL);
107109
if (authenticatorListener != null) {
108110
authenticatorListener.onLoginSuccess(caller.getUid(), token);
@@ -127,11 +129,12 @@ public String signJWT(String username, String pwd, E metaData, int validForMinut
127129
* customized token format
128130
*
129131
* @param caller
132+
* @param txId
130133
* @return formatted auth token builder
131134
*/
132135
@Override
133-
public JwtBuilder toJwt(Caller caller) {
134-
String jti = caller.getTenantId() + "." + caller.getId() + "_" + caller.getUid() + "_" + System.currentTimeMillis();
136+
public JwtBuilder toJwt(Caller caller, String txId) {
137+
String jti = caller.getTenantId() + "-" + caller.getId() + "@" + txId; // tenantId-userId@txId
135138
String issuer = AuthConfig.cfg.getJwtIssuer();
136139
String userName = caller.getUid();
137140
Set<String> groups = caller.getGroups();
@@ -145,24 +148,31 @@ public JwtBuilder toJwt(Caller caller) {
145148
.issuer(issuer)
146149
.subject(userName)
147150
.audience().add(groups);
148-
if (caller.getId() != null) {
149-
builder.claim("callerId", caller.getId());
151+
// if (caller.getId() != null) {
152+
// builder.claim(KEY_CALLERID, caller.getId());
153+
// }
154+
// if (caller.getTenantId() != null) {
155+
// builder.claim(KEY_TENANTID, caller.getTenantId());
156+
// }
157+
if (StringUtils.isNotBlank(caller.getTenantName())) {
158+
builder.claim(KEY_TENANTNAME, caller.getTenantName());
150159
}
151-
if (caller.getTenantId() != null) {
152-
builder.claim("tenantId", caller.getTenantId());
153-
}
154-
if (caller.getTenantName() != null) {
155-
builder.claim("tenantName", caller.getTenantName());
156-
}
157-
Set<String> keys = caller.propKeySet();
158-
if (keys != null) {
159-
for (String key : keys) {
160-
Object v = caller.getProp(key, Object.class);
161-
builder.claim(key, v);
160+
Set<Map.Entry<String, Object>> callerCustomizedFields = caller.customizedFields();
161+
if (callerCustomizedFields != null) {
162+
for (Map.Entry<String, Object> entry : callerCustomizedFields) {
163+
if (StringUtils.isBlank(entry.getKey()) || entry.getValue() == null) {
164+
continue;
165+
}
166+
builder.claim(entry.getKey(), entry.getValue());
162167
}
163168
}
164169

165170
//JwtBuilder builder = Jwts.builder().setClaims(claims);
171+
Long tokenTtlSec = caller.getTokenTtlSec();
172+
if (tokenTtlSec != null) {
173+
Duration tokenTTL = Duration.ofSeconds(tokenTtlSec);
174+
JwtUtil.setJwtExpireTime(builder, tokenTTL);
175+
}
166176
return builder;
167177
}
168178

@@ -174,6 +184,10 @@ protected Claims parseJWT(String jwt) {
174184
return JwtUtil.parseJWT(jwtParser, jwt).getPayload();
175185
}
176186

187+
// private static final String KEY_CALLERID = "callerId";
188+
// private static final String KEY_TENANTID = "tenantId";
189+
private static final String KEY_TENANTNAME = "tenantName";
190+
177191
/**
178192
* Convert Caller back from auth token, override this method to implement
179193
* customized token format
@@ -182,39 +196,61 @@ protected Claims parseJWT(String jwt) {
182196
* @return Caller
183197
*/
184198
protected Caller fromJwt(Claims claims) {
185-
//String jti = claims.getId();
186199
//String issuer = claims.getIssuer();
187200
String userName = claims.getSubject();
188201
Set<String> audience = claims.getAudience();
189-
Long userId = claims.get("callerId", Long.class);
190-
Long tenantId = claims.get("tenantId", Long.class);
191-
String tenantName = claims.get("tenantName", String.class);
202+
// Long userId = claims.get(KEY_CALLERID, Long.class);
203+
// Long tenantId = claims.get(KEY_TENANTID, Long.class);
204+
String jti = claims.getId(); // tenantId-userId@txId
205+
Long tenantId = 0L, userId = 0L;
206+
// parse jti into tenantId and userId
207+
try {
208+
String[] arr0 = FormatterUtil.parseDsv(jti, "-");
209+
if (arr0 != null && arr0.length > 1) {
210+
tenantId = Long.parseLong(arr0[0]);
211+
String[] arr1 = FormatterUtil.parseDsv(arr0[1], "@");
212+
userId = Long.parseLong(arr1[0]);
213+
}
214+
} catch (Exception ex) {
215+
//ignore
216+
}
217+
String tenantName = claims.get(KEY_TENANTNAME, String.class);
192218

193219
User caller = new User(tenantId, tenantName, userId, userName);
194220

195221
if (audience != null) {
196222
for (String group : audience) {
197223
caller.addGroup(group);
224+
if (BootConstant.CFG_JWT_AUD_AS_CSV && audience.size() == 1) {
225+
String[] arr = FormatterUtil.parseCsv(group);
226+
if (arr != null) {
227+
for (String g : arr) {
228+
if (StringUtils.isNotBlank(g)) {
229+
caller.addGroup(g);
230+
}
231+
}
232+
}
233+
}
198234
}
199235
}
200236

201237
Set<String> keys = claims.keySet();
202238
if (keys != null) {
203239
for (String key : keys) {
204240
Object v = claims.get(key);
205-
caller.putProp(key, v);
241+
caller.setCustomizedField(key, v);
206242
}
207243
}
208-
caller.remove(Claims.AUDIENCE);
209-
caller.remove(Claims.EXPIRATION);
210-
caller.remove(Claims.ID);
211-
caller.remove(Claims.ISSUED_AT);
212-
caller.remove(Claims.ISSUER);
213-
caller.remove(Claims.NOT_BEFORE);
214-
caller.remove(Claims.SUBJECT);
215-
caller.remove("callerId");
216-
caller.remove("tenantId");
217-
caller.remove("tenantName");
244+
caller.removeCustomizedField(Claims.AUDIENCE);
245+
caller.removeCustomizedField(Claims.EXPIRATION);
246+
caller.removeCustomizedField(Claims.ID);
247+
caller.removeCustomizedField(Claims.ISSUED_AT);
248+
caller.removeCustomizedField(Claims.ISSUER);
249+
caller.removeCustomizedField(Claims.NOT_BEFORE);
250+
caller.removeCustomizedField(Claims.SUBJECT);
251+
// caller.removeCustomizedField(KEY_CALLERID);
252+
// caller.removeCustomizedField(KEY_TENANTID);
253+
caller.removeCustomizedField(KEY_TENANTNAME);
218254

219255
return caller;
220256
}

0 commit comments

Comments
 (0)