Skip to content

Commit

Permalink
Feature2.4.17 (#289)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
SummerBootFramework authored Nov 27, 2024
1 parent 57569b1 commit fcd5f98
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 161 deletions.
36 changes: 18 additions & 18 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.summerboot</groupId>
<artifactId>jexpress</artifactId>
<version>2.4.16</version>
<version>2.4.17</version>
<packaging>jar</packaging>
<name>Summer Boot jExpress</name>
<description>Summer Boot jExpress focuses on solving non-functional and operational maintainability requirements,
Expand Down Expand Up @@ -190,56 +190,56 @@
<!-- Commons -->
<commons-lang3.version>3.17.0</commons-lang3.version>
<commons-cli.version>1.9.0</commons-cli.version>
<commons-io.version>2.17.0</commons-io.version>
<commons-io.version>2.18.0</commons-io.version>
<!-- <commons-text.version>1.11.0</commons-text.version>-->
<!-- <owasp.encoder.version>1.2.3</owasp.encoder.version>-->
<!-- Logging -->
<log4j-api.version>2.24.1</log4j-api.version>
<log4j-api.version>2.24.2</log4j-api.version>
<log4j-disruptor.version>4.0.0</log4j-disruptor.version>
<!-- Mail -->
<jakarta-mail.version>2.0.1</jakarta-mail.version>
<!-- Security -->
<bouncycastle.version>1.78.1</bouncycastle.version>
<bouncycastle.version>1.79</bouncycastle.version>
<!-- JWT -->
<jwt.version>0.12.6</jwt.version>

<!-- NIO Netty -->
<netty.version>4.1.114.Final</netty.version>
<netty-tcnative.version>2.0.66.Final</netty-tcnative.version>
<netty.version>4.1.115.Final</netty.version>
<netty-tcnative.version>2.0.69.Final</netty-tcnative.version>
<!-- gRPC and protobuf -->
<grpc.version>1.68.0</grpc.version>
<grpc.version>1.68.1</grpc.version>
<guava.version>33.3.1-jre</guava.version>
<protobuf.version>4.28.2</protobuf.version>
<protobuf.version>4.28.3</protobuf.version>
<!-- Web JAX-RS -->
<swagger.core.version>2.2.23</swagger.core.version>
<swagger.core.version>2.2.26</swagger.core.version>
<!--<elastic-apm.version>1.36.0</elastic-apm.version>-->


<!-- MIME-Type -->
<tika.version>2.9.2</tika.version>
<tika.version>3.0.0</tika.version>
<!-- JAX-RS -->
<rs-api.version>4.0.0</rs-api.version>
<jakarta.annotation.version>3.0.0</jakarta.annotation.version>
<reflections.version>0.10.2</reflections.version>

<!-- JSON/XML/Bean Validation -->
<jackson.version>2.18.0</jackson.version>
<jackson.version>2.18.1</jackson.version>
<!-- Bean Validation -->
<jakarta.el.version>6.0.1</jakarta.el.version>
<tomcat-embed-el.version>11.0.0-M24</tomcat-embed-el.version>
<tomcat-embed-el.version>11.0.1</tomcat-embed-el.version>
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>

<!-- IOC Injection -->
<guice.version>7.0.0</guice.version>

<!-- JPA -->
<hibernate.version>6.6.1.Final</hibernate.version>
<hikari-cp.version>6.0.0</hikari-cp.version>
<hibernate.version>6.6.3.Final</hibernate.version>
<hikari-cp.version>6.2.1</hikari-cp.version>

<!-- Cache -->
<jedis.version>5.2.0</jedis.version>

<quartz.version>2.5.0-rc1</quartz.version>
<quartz.version>2.5.0</quartz.version>
<mqtt.version>1.2.5</mqtt.version>

<!-- Template Engine -->
Expand All @@ -252,12 +252,12 @@
<openhtml.version>1.0.10</openhtml.version>
<batik-transcoder.version>1.18</batik-transcoder.version>
<!-- PDF - iText -->
<itext7-core.version>8.0.5</itext7-core.version>
<itext7-html2pdf.version>5.0.5</itext7-html2pdf.version>
<itext7-core.version>9.0.0</itext7-core.version>
<itext7-html2pdf.version>6.0.0</itext7-html2pdf.version>

<!-- Testing -->
<testng.version>7.10.2</testng.version>
<jdbc.version>9.0.0</jdbc.version>
<jdbc.version>9.1.0</jdbc.version>
</properties>

<dependencies>
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/summerboot/jexpress/boot/BackOffice.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ public Map<Integer, Integer> getBootErrorCodeMapping() {
@Config(key = "type.errorCodeAsInt", defaultValue = "false")
private boolean errorCodeAsInt = false;

@Config(key = "type.JWTAudAsCSV", defaultValue = "true", desc = "Parse JWT Audience value as CSV for JWT backward compatibility")
private boolean jwtAudAsCSV = true;

@Config(key = "default.interval.ConfigChangeMonitor", defaultValue = "30")
private int CfgChangeMonitorIntervalSec = 30;

Expand Down Expand Up @@ -320,6 +323,10 @@ public boolean isErrorCodeAsInt() {
return errorCodeAsInt;
}

public boolean isJwtAudAsCSV() {
return jwtAudAsCSV;
}

public int getCfgChangeMonitorIntervalSec() {
return CfgChangeMonitorIntervalSec;
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/summerboot/jexpress/boot/BootConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public interface BootConstant {
String APP_ID = String.format("%06d", new Random().nextInt(999999));

//version
String VERSION = "jExpress 2.4.16";
String VERSION = "jExpress 2.4.17";
String JEXPRESS_PACKAGE_NAME = "org.summerboot.jexpress";

String DEFAULT_ADMIN_MM = "changeit";
Expand All @@ -48,6 +48,7 @@ public interface BootConstant {
* 3. jExpress Default Settings
*/
boolean CFG_ERROR_CODE_AS_INT = BackOffice.agent.isErrorCodeAsInt();
boolean CFG_JWT_AUD_AS_CSV = BackOffice.agent.isJwtAudAsCSV();
int CFG_CHANGE_MONITOR_INTERVAL_SEC = BackOffice.agent.getCfgChangeMonitorIntervalSec();
int PACKAGE_LEVEL = BackOffice.agent.getReflectionPackageLevel();
long WEB_RESOURCE_TTL_MS = BackOffice.agent.getWebResourceCacheTtlSec() * 1000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
abstract public class BootController extends PingController {

public static final String TAG_APP_ADMIN = "App Admin";
public static final String TAG_USER_AUTH = "App User Authentication";
public static final String TAG_USER_AUTH = "App Authentication";


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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,26 @@ public interface Authenticator<T> {
*/
String signJWT(String username, String pwd, T metaData, int validForMinutes, final ServiceContext context) throws NamingException;


/**
* Success HTTP Status: 201 Created
*
* @param caller
* @param validForMinutes
* @param context
* @return
*/
String signJWT(Caller caller, int validForMinutes, final ServiceContext context);

/**
* Convert Caller to auth token, override this method to implement
* customized token format
*
* @param caller
* @param txId
* @return formatted auth token builder
*/
JwtBuilder toJwt(Caller caller);
JwtBuilder toJwt(Caller caller, String txId);

/**
* Success HTTP Status: 200 OK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.apache.commons.lang3.StringUtils;
import org.summerboot.jexpress.boot.BootConstant;
import org.summerboot.jexpress.boot.BootErrorCode;
import org.summerboot.jexpress.boot.BootPOI;
import org.summerboot.jexpress.integration.cache.AuthTokenCache;
Expand All @@ -42,11 +43,13 @@
import org.summerboot.jexpress.nio.server.domain.Err;
import org.summerboot.jexpress.nio.server.domain.ServiceContext;
import org.summerboot.jexpress.security.JwtUtil;
import org.summerboot.jexpress.util.FormatterUtil;

import javax.naming.NamingException;
import java.security.Key;
import java.time.Duration;
import java.util.Date;
import java.util.Map;
import java.util.Set;

/**
Expand Down Expand Up @@ -81,28 +84,27 @@ public String signJWT(String username, String pwd, E metaData, int validForMinut
context.poi(BootPOI.LDAP_BEGIN);
Caller caller = authenticate(username, pwd, (E) metaData, authenticatorListener, context);
context.poi(BootPOI.LDAP_END);

return signJWT(caller, validForMinutes, context);
}

@Override
public String signJWT(Caller caller, int validForMinutes, final ServiceContext context) {
if (caller == null) {
context.status(HttpResponseStatus.UNAUTHORIZED);
return null;
}

// get token TTL from caller, otherwise use default
Long tokenTtlSec = caller.getTokenTtlSec();
Duration tokenTTL;
if (tokenTtlSec != null) {
tokenTTL = Duration.ofSeconds(tokenTtlSec);
} else {
tokenTTL = Duration.ofMinutes(validForMinutes);
}

//3. format JWT
JwtBuilder builder = toJwt(caller);
//3. format JWT and set token TTL from caller
JwtBuilder builder = toJwt(caller, context.txId());

//4. create JWT
//5. create JWT
Key signingKey = AuthConfig.cfg.getJwtSigningKey();
if (signingKey == null) {
throw new UnsupportedOperationException(ERROR_NO_CFG);
}
// override caller TTL if validForMinutes is greater than 0
Duration tokenTTL = Duration.ofMinutes(validForMinutes);
String token = JwtUtil.createJWT(signingKey, builder, tokenTTL);
if (authenticatorListener != null) {
authenticatorListener.onLoginSuccess(caller.getUid(), token);
Expand All @@ -127,11 +129,12 @@ public String signJWT(String username, String pwd, E metaData, int validForMinut
* customized token format
*
* @param caller
* @param txId
* @return formatted auth token builder
*/
@Override
public JwtBuilder toJwt(Caller caller) {
String jti = caller.getTenantId() + "." + caller.getId() + "_" + caller.getUid() + "_" + System.currentTimeMillis();
public JwtBuilder toJwt(Caller caller, String txId) {
String jti = caller.getTenantId() + "-" + caller.getId() + "@" + txId; // tenantId-userId@txId
String issuer = AuthConfig.cfg.getJwtIssuer();
String userName = caller.getUid();
Set<String> groups = caller.getGroups();
Expand All @@ -145,24 +148,31 @@ public JwtBuilder toJwt(Caller caller) {
.issuer(issuer)
.subject(userName)
.audience().add(groups);
if (caller.getId() != null) {
builder.claim("callerId", caller.getId());
// if (caller.getId() != null) {
// builder.claim(KEY_CALLERID, caller.getId());
// }
// if (caller.getTenantId() != null) {
// builder.claim(KEY_TENANTID, caller.getTenantId());
// }
if (StringUtils.isNotBlank(caller.getTenantName())) {
builder.claim(KEY_TENANTNAME, caller.getTenantName());
}
if (caller.getTenantId() != null) {
builder.claim("tenantId", caller.getTenantId());
}
if (caller.getTenantName() != null) {
builder.claim("tenantName", caller.getTenantName());
}
Set<String> keys = caller.propKeySet();
if (keys != null) {
for (String key : keys) {
Object v = caller.getProp(key, Object.class);
builder.claim(key, v);
Set<Map.Entry<String, Object>> callerCustomizedFields = caller.customizedFields();
if (callerCustomizedFields != null) {
for (Map.Entry<String, Object> entry : callerCustomizedFields) {
if (StringUtils.isBlank(entry.getKey()) || entry.getValue() == null) {
continue;
}
builder.claim(entry.getKey(), entry.getValue());
}
}

//JwtBuilder builder = Jwts.builder().setClaims(claims);
Long tokenTtlSec = caller.getTokenTtlSec();
if (tokenTtlSec != null) {
Duration tokenTTL = Duration.ofSeconds(tokenTtlSec);
JwtUtil.setJwtExpireTime(builder, tokenTTL);
}
return builder;
}

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

// private static final String KEY_CALLERID = "callerId";
// private static final String KEY_TENANTID = "tenantId";
private static final String KEY_TENANTNAME = "tenantName";

/**
* Convert Caller back from auth token, override this method to implement
* customized token format
Expand All @@ -182,39 +196,61 @@ protected Claims parseJWT(String jwt) {
* @return Caller
*/
protected Caller fromJwt(Claims claims) {
//String jti = claims.getId();
//String issuer = claims.getIssuer();
String userName = claims.getSubject();
Set<String> audience = claims.getAudience();
Long userId = claims.get("callerId", Long.class);
Long tenantId = claims.get("tenantId", Long.class);
String tenantName = claims.get("tenantName", String.class);
// Long userId = claims.get(KEY_CALLERID, Long.class);
// Long tenantId = claims.get(KEY_TENANTID, Long.class);
String jti = claims.getId(); // tenantId-userId@txId
Long tenantId = 0L, userId = 0L;
// parse jti into tenantId and userId
try {
String[] arr0 = FormatterUtil.parseDsv(jti, "-");
if (arr0 != null && arr0.length > 1) {
tenantId = Long.parseLong(arr0[0]);
String[] arr1 = FormatterUtil.parseDsv(arr0[1], "@");
userId = Long.parseLong(arr1[0]);
}
} catch (Exception ex) {
//ignore
}
String tenantName = claims.get(KEY_TENANTNAME, String.class);

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

if (audience != null) {
for (String group : audience) {
caller.addGroup(group);
if (BootConstant.CFG_JWT_AUD_AS_CSV && audience.size() == 1) {
String[] arr = FormatterUtil.parseCsv(group);
if (arr != null) {
for (String g : arr) {
if (StringUtils.isNotBlank(g)) {
caller.addGroup(g);
}
}
}
}
}
}

Set<String> keys = claims.keySet();
if (keys != null) {
for (String key : keys) {
Object v = claims.get(key);
caller.putProp(key, v);
caller.setCustomizedField(key, v);
}
}
caller.remove(Claims.AUDIENCE);
caller.remove(Claims.EXPIRATION);
caller.remove(Claims.ID);
caller.remove(Claims.ISSUED_AT);
caller.remove(Claims.ISSUER);
caller.remove(Claims.NOT_BEFORE);
caller.remove(Claims.SUBJECT);
caller.remove("callerId");
caller.remove("tenantId");
caller.remove("tenantName");
caller.removeCustomizedField(Claims.AUDIENCE);
caller.removeCustomizedField(Claims.EXPIRATION);
caller.removeCustomizedField(Claims.ID);
caller.removeCustomizedField(Claims.ISSUED_AT);
caller.removeCustomizedField(Claims.ISSUER);
caller.removeCustomizedField(Claims.NOT_BEFORE);
caller.removeCustomizedField(Claims.SUBJECT);
// caller.removeCustomizedField(KEY_CALLERID);
// caller.removeCustomizedField(KEY_TENANTID);
caller.removeCustomizedField(KEY_TENANTNAME);

return caller;
}
Expand Down
Loading

0 comments on commit fcd5f98

Please sign in to comment.