diff --git a/docs-web-common/pom.xml b/docs-web-common/pom.xml
index c1ff6b9e..9cbd5bb2 100644
--- a/docs-web-common/pom.xml
+++ b/docs-web-common/pom.xml
@@ -68,6 +68,27 @@
org.slf4j
jul-to-slf4j
+
+
+ io.github.openfeign
+ feign-okhttp
+ 13.0
+
+
+ io.github.openfeign
+ feign-gson
+ 13.0
+
+
+ io.github.openfeign
+ feign-slf4j
+ 13.0
+
+
+ com.auth0
+ java-jwt
+ 4.4.0
+
diff --git a/docs-web-common/src/main/java/com/sismics/feign/KeycloakClient.java b/docs-web-common/src/main/java/com/sismics/feign/KeycloakClient.java
new file mode 100644
index 00000000..ba5b5efe
--- /dev/null
+++ b/docs-web-common/src/main/java/com/sismics/feign/KeycloakClient.java
@@ -0,0 +1,10 @@
+package com.sismics.feign;
+
+import com.sismics.feign.model.KeycloakCertKeys;
+import feign.RequestLine;
+
+public interface KeycloakClient {
+
+ @RequestLine("GET /protocol/openid-connect/certs")
+ KeycloakCertKeys getCert();
+}
diff --git a/docs-web-common/src/main/java/com/sismics/feign/model/KeycloakCertKey.java b/docs-web-common/src/main/java/com/sismics/feign/model/KeycloakCertKey.java
new file mode 100644
index 00000000..ef25544b
--- /dev/null
+++ b/docs-web-common/src/main/java/com/sismics/feign/model/KeycloakCertKey.java
@@ -0,0 +1,27 @@
+package com.sismics.feign.model;
+
+import java.util.List;
+
+public class KeycloakCertKey {
+ public String kid;
+ public List x5c;
+
+ public KeycloakCertKey() {
+ }
+
+ public List getX5c() {
+ return x5c;
+ }
+
+ public void setX5c(List x5c) {
+ this.x5c = x5c;
+ }
+
+ public String getKid() {
+ return kid;
+ }
+
+ public void setKid(String kid) {
+ this.kid = kid;
+ }
+}
diff --git a/docs-web-common/src/main/java/com/sismics/feign/model/KeycloakCertKeys.java b/docs-web-common/src/main/java/com/sismics/feign/model/KeycloakCertKeys.java
new file mode 100644
index 00000000..8cf387e1
--- /dev/null
+++ b/docs-web-common/src/main/java/com/sismics/feign/model/KeycloakCertKeys.java
@@ -0,0 +1,18 @@
+package com.sismics.feign.model;
+
+import java.util.List;
+
+public class KeycloakCertKeys {
+ public List keys;
+
+ public KeycloakCertKeys() {
+ }
+
+ public List getKeys() {
+ return keys;
+ }
+
+ public void setKeys(List keys) {
+ this.keys = keys;
+ }
+}
diff --git a/docs-web-common/src/main/java/com/sismics/util/filter/JwtBasedSecurityFilter.java b/docs-web-common/src/main/java/com/sismics/util/filter/JwtBasedSecurityFilter.java
new file mode 100644
index 00000000..f7ece132
--- /dev/null
+++ b/docs-web-common/src/main/java/com/sismics/util/filter/JwtBasedSecurityFilter.java
@@ -0,0 +1,131 @@
+package com.sismics.util.filter;
+
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.impl.JWTParser;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.auth0.jwt.interfaces.JWTVerifier;
+import java.util.Base64;
+import com.sismics.docs.core.constant.Constants;
+import com.sismics.docs.core.dao.UserDao;
+import com.sismics.docs.core.model.jpa.User;
+import com.sismics.feign.KeycloakClient;
+import feign.Feign;
+import feign.gson.GsonDecoder;
+import feign.gson.GsonEncoder;
+import feign.okhttp.OkHttpClient;
+import feign.slf4j.Slf4jLogger;
+import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Objects;
+import java.util.UUID;
+
+import static java.util.Optional.ofNullable;
+
+/**
+ * This filter is used to authenticate the user having an active session by validating a jwt token.
+ * The filter extracts the jwt token stored from Authorization header.
+ * It validates the token by calling an Identity Broker like KeyCloak.
+ * If validated, the user is retrieved, and the filter injects a UserPrincipal into the request attribute.
+ *
+ * @author smitra
+ */
+public class JwtBasedSecurityFilter extends SecurityFilter {
+ private static final Logger log = LoggerFactory.getLogger(JwtBasedSecurityFilter.class);
+ /**
+ * Name of the cookie used to store the authentication token.
+ */
+ public static final String HEADER_NAME = "Authorization";
+
+ @Override
+ protected User authenticate(final HttpServletRequest request) {
+ log.info("Jwt authentication started");
+ User user = null;
+ String token = extractAuthToken(request).replace("Bearer ", "");
+ DecodedJWT jwt = JWT.decode(token);
+ if (verifyJwt(jwt, token)) {
+ String email = jwt.getClaim("preferred_username").toString();
+ UserDao userDao = new UserDao();
+ user = userDao.getActiveByUsername(email);
+ if (user == null) {
+ user = new User();
+ user.setRoleId(Constants.DEFAULT_USER_ROLE);
+ user.setUsername(email);
+ user.setEmail(email);
+ user.setStorageQuota(10L);
+ user.setPassword(UUID.randomUUID().toString());
+ try {
+ userDao.create(user, email);
+ log.info("user created");
+ } catch (Exception e) {
+ log.info("Error:" + e.getMessage());
+ return null;
+ }
+ }
+ }
+ return user;
+ }
+
+ private boolean verifyJwt(final DecodedJWT jwt, final String token) {
+
+ try {
+ buildJWTVerifier(jwt).verify(token);
+ // if token is valid no exception will be thrown
+ log.info("Valid TOKEN");
+ return Boolean.TRUE;
+ } catch (CertificateException e) {
+ //if CertificateException comes from buildJWTVerifier()
+ log.info("InValid TOKEN");
+ e.printStackTrace();
+ return Boolean.FALSE;
+ } catch (JWTVerificationException e) {
+ // if JWT Token in invalid
+ log.info("InValid TOKEN");
+ e.printStackTrace();
+ return Boolean.FALSE;
+ } catch (Exception e) {
+ // If any other exception comes
+ log.info("InValid TOKEN, Exception Occurred");
+ e.printStackTrace();
+ return Boolean.FALSE;
+ }
+ }
+
+ private String extractAuthToken(final HttpServletRequest request) {
+ return ofNullable(request.getHeader("Authorization")).orElse("");
+ }
+
+ private RSAPublicKey getPublicKey(DecodedJWT jwt) {
+ KeycloakClient client = Feign.builder()
+ .client(new OkHttpClient())
+ .encoder(new GsonEncoder())
+ .decoder(new GsonDecoder())
+ .logLevel(feign.Logger.Level.BASIC)
+ .logger(new Slf4jLogger(KeycloakClient.class))
+ .target(KeycloakClient.class, jwt.getIssuer());
+ String publicKey = client.getCert().getKeys().stream().filter(k -> Objects.equals(k.getKid(), jwt.getKeyId()))
+ .findFirst()
+ .map(k -> k.getX5c().get(0))
+ .orElse("");
+ try {
+ var decode = Base64.getDecoder().decode(publicKey);
+ var certificate = CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(decode));
+ return (RSAPublicKey)certificate.getPublicKey();
+ } catch (CertificateException ex) {
+ return null;
+ }
+ }
+
+ private JWTVerifier buildJWTVerifier(DecodedJWT jwt) throws CertificateException {
+ var algo = Algorithm.RSA256(getPublicKey(jwt), null);
+ return JWT.require(algo).build();
+ }
+}
diff --git a/docs-web/src/main/webapp/WEB-INF/web.xml b/docs-web/src/main/webapp/WEB-INF/web.xml
index 720b328e..e5c06e24 100644
--- a/docs-web/src/main/webapp/WEB-INF/web.xml
+++ b/docs-web/src/main/webapp/WEB-INF/web.xml
@@ -44,6 +44,12 @@
true
+
+ jwtBasedSecurityFilter
+ com.sismics.util.filter.JwtBasedSecurityFilter
+ true
+
+
headerBasedSecurityFilter
com.sismics.util.filter.HeaderBasedSecurityFilter
@@ -59,6 +65,11 @@
/api/*
+
+ jwtBasedSecurityFilter
+ /api/*
+
+
headerBasedSecurityFilter
/api/*