mirror of
https://github.com/sismics/docs.git
synced 2025-01-09 19:55:16 +01:00
Added support for jwt based calls
This commit is contained in:
parent
428e898a7a
commit
fb45d7d7ac
@ -69,6 +69,27 @@
|
|||||||
<artifactId>jul-to-slf4j</artifactId>
|
<artifactId>jul-to-slf4j</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.openfeign</groupId>
|
||||||
|
<artifactId>feign-okhttp</artifactId>
|
||||||
|
<version>13.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.openfeign</groupId>
|
||||||
|
<artifactId>feign-gson</artifactId>
|
||||||
|
<version>13.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.openfeign</groupId>
|
||||||
|
<artifactId>feign-slf4j</artifactId>
|
||||||
|
<version>13.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
<version>4.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Test dependencies -->
|
<!-- Test dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
@ -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();
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.sismics.feign.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KeycloakCertKey {
|
||||||
|
public String kid;
|
||||||
|
public List<String> x5c;
|
||||||
|
|
||||||
|
public KeycloakCertKey() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getX5c() {
|
||||||
|
return x5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setX5c(List<String> x5c) {
|
||||||
|
this.x5c = x5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKid() {
|
||||||
|
return kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKid(String kid) {
|
||||||
|
this.kid = kid;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.sismics.feign.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KeycloakCertKeys {
|
||||||
|
public List<KeycloakCertKey> keys;
|
||||||
|
|
||||||
|
public KeycloakCertKeys() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KeycloakCertKey> getKeys() {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeys(List<KeycloakCertKey> keys) {
|
||||||
|
this.keys = keys;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,12 @@
|
|||||||
<async-supported>true</async-supported>
|
<async-supported>true</async-supported>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<filter-name>jwtBasedSecurityFilter</filter-name>
|
||||||
|
<filter-class>com.sismics.util.filter.JwtBasedSecurityFilter</filter-class>
|
||||||
|
<async-supported>true</async-supported>
|
||||||
|
</filter>
|
||||||
|
|
||||||
<filter>
|
<filter>
|
||||||
<filter-name>headerBasedSecurityFilter</filter-name>
|
<filter-name>headerBasedSecurityFilter</filter-name>
|
||||||
<filter-class>com.sismics.util.filter.HeaderBasedSecurityFilter</filter-class>
|
<filter-class>com.sismics.util.filter.HeaderBasedSecurityFilter</filter-class>
|
||||||
@ -59,6 +65,11 @@
|
|||||||
<url-pattern>/api/*</url-pattern>
|
<url-pattern>/api/*</url-pattern>
|
||||||
</filter-mapping>
|
</filter-mapping>
|
||||||
|
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>jwtBasedSecurityFilter</filter-name>
|
||||||
|
<url-pattern>/api/*</url-pattern>
|
||||||
|
</filter-mapping>
|
||||||
|
|
||||||
<filter-mapping>
|
<filter-mapping>
|
||||||
<filter-name>headerBasedSecurityFilter</filter-name>
|
<filter-name>headerBasedSecurityFilter</filter-name>
|
||||||
<url-pattern>/api/*</url-pattern>
|
<url-pattern>/api/*</url-pattern>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user