From b8774f12f3f2c4faafcfcfe4ad5d599b3e83d752 Mon Sep 17 00:00:00 2001 From: emilioweber Date: Wed, 17 Apr 2019 15:00:50 -0300 Subject: [PATCH 1/2] Adding request filter --- README.md | 11 +- pom.xml | 6 + .../SymExtensionAppRSAAuth.java | 19 +++ .../jwt/AuthenticationFilter.java | 107 ++++++++++++++++ .../java/authentication/jwt/JwtPayload.java | 60 +++++++++ src/main/java/authentication/jwt/JwtUser.java | 118 ++++++++++++++++++ src/main/java/configuration/SymConfig.java | 10 ++ 7 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 src/main/java/authentication/jwt/AuthenticationFilter.java create mode 100644 src/main/java/authentication/jwt/JwtPayload.java create mode 100644 src/main/java/authentication/jwt/JwtUser.java diff --git a/README.md b/README.md index 2b571b836..500d34f3e 100755 --- a/README.md +++ b/README.md @@ -74,7 +74,10 @@ can exclude the bot certificate section, all extension app sections and all opti // Optional: To modify the default datafeed handling properties "datafeedEventsThreadpoolSize": 5, - "datafeedEventsErrorTimeout": 30 + "datafeedEventsErrorTimeout": 30, + + // Optional: Request filter to verify JWT + "authenticationFilterUrlPattern": "/v1/", } ``` @@ -112,6 +115,12 @@ SymBotAuth botAuth = new SymBotAuth(config, sessionAuthClientConfig, kmAuthClien ``` +## Request Filter +Provides a filter component to validate requests based on their JWT. To enable this feature, it's required that you instantiate the AuthenticationFilter (see example below) and a URL pattern which the filter will be applied to. +``` +AuthenticationFilter = new AuthenticationFilter(symExtensionAppRSAAuth, symConfig); +``` + ## Example main class ```java diff --git a/pom.xml b/pom.xml index 3b32f115f..18f35cbdf 100755 --- a/pom.xml +++ b/pom.xml @@ -191,6 +191,12 @@ jose4j 0.6.3 + + javax.servlet + servlet-api + 2.5 + provided + diff --git a/src/main/java/authentication/SymExtensionAppRSAAuth.java b/src/main/java/authentication/SymExtensionAppRSAAuth.java index 500747add..11ffc2c83 100644 --- a/src/main/java/authentication/SymExtensionAppRSAAuth.java +++ b/src/main/java/authentication/SymExtensionAppRSAAuth.java @@ -11,6 +11,7 @@ import exceptions.NoConfigException; import model.AppAuthResponse; import model.PodCert; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; import org.glassfish.jersey.client.ClientConfig; @@ -20,11 +21,16 @@ import utils.HttpClientBuilderHelper; import utils.JwtHelper; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -45,6 +51,10 @@ public final class SymExtensionAppRSAAuth extends APIClient { private TokensRepository tokensRepository; private String podCertificate; + public PublicKey getPodPublicKey() throws CertificateException { + return readPublicKey(); + } + public SymExtensionAppRSAAuth(final SymConfig configuration) { this.config = configuration; ClientBuilder clientBuilder = @@ -171,4 +181,13 @@ public Object verifyJWT(final String jwt) { } return null; } + + private PublicKey readPublicKey() throws CertificateException { + String encoded = podCertificate.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", ""); + byte[] decoded = Base64.decodeBase64(encoded); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate + x509Certificate = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(decoded)); + return x509Certificate.getPublicKey(); + } } diff --git a/src/main/java/authentication/jwt/AuthenticationFilter.java b/src/main/java/authentication/jwt/AuthenticationFilter.java new file mode 100644 index 000000000..54445f556 --- /dev/null +++ b/src/main/java/authentication/jwt/AuthenticationFilter.java @@ -0,0 +1,107 @@ +package authentication.jwt; + +import authentication.SymExtensionAppRSAAuth; +import configuration.SymConfig; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.util.regex.Pattern; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class AuthenticationFilter implements Filter { + + private static final String FILTER_INIT = "Starting authentication filter"; + private static final String FILTER_DESTROY = "Destroying authentication filter"; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String AUTHORIZATION_HEADER_BEARER_PREFIX = "BEARER"; + private static final String USER_INFO_PROPERTY = "user_info"; + private static final String MISSING_JWT_MESSAGE = "Missing JWT"; + private static final String UNAUTHORIZED_JWT_MESSAGE = "Unauthorized JWT"; + private static final String INTERNAL_SERVER_ERROR_MESSAGE = + "Unexpected error, please contact the system administrator"; + private static final String USER_FIELDNAME = "user"; + private static final String USER_ID_FIELDNAME = "sub"; + private static final String ANY_FOLLOWING_CHARACTER_PATTERN = ".*"; + + private SymExtensionAppRSAAuth rsaAuth; + private SymConfig symConfig; + private JwtParser parser = Jwts.parser(); + + private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationFilter.class); + + public AuthenticationFilter(SymExtensionAppRSAAuth rsaAuth, SymConfig symConfig) { + this.rsaAuth = rsaAuth; + this.symConfig = symConfig; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + LoggerFactory.getLogger(AuthenticationFilter.class).info(FILTER_INIT); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, + FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + if (Pattern.compile( + symConfig.getAuthenticationFilterUrlPattern() + ANY_FOLLOWING_CHARACTER_PATTERN) + .matcher(request.getServletPath()) + .matches()) { + String jwt = + request.getHeader(AUTHORIZATION_HEADER) + .substring(AUTHORIZATION_HEADER_BEARER_PREFIX.length()); + if (jwt == null) { + response.setStatus(401); + response.getWriter().write(MISSING_JWT_MESSAGE); + return; + } + + Jws jws; + try { + PublicKey rsaVerifier = rsaAuth.getPodPublicKey(); + jws = parser.setSigningKey(rsaVerifier).parseClaimsJws(jwt); + } catch (CertificateException e) { + response.setStatus(401); + response.getWriter().write(UNAUTHORIZED_JWT_MESSAGE); + return; + } + + try { + new JwtPayload(); + JwtPayload jwtPayload = new JwtPayload(); + jwtPayload.setUserId(String.valueOf(jws.getBody().get(USER_ID_FIELDNAME))); + request.setAttribute(USER_INFO_PROPERTY, jwtPayload); + + filterChain.doFilter(request, servletResponse); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + response.setStatus(500); + response.getWriter().write(INTERNAL_SERVER_ERROR_MESSAGE); + return; + } + } + filterChain.doFilter(request, servletResponse); + } + + @Override + public void destroy() { + LoggerFactory.getLogger(AuthenticationFilter.class).info(FILTER_DESTROY); + } +} diff --git a/src/main/java/authentication/jwt/JwtPayload.java b/src/main/java/authentication/jwt/JwtPayload.java new file mode 100644 index 000000000..7a64c56bf --- /dev/null +++ b/src/main/java/authentication/jwt/JwtPayload.java @@ -0,0 +1,60 @@ +package authentication.jwt; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class JwtPayload { + @JsonProperty("sub") + private String userId; + private JwtUser user; + @JsonProperty("aud") + private String applicationId; + @JsonProperty("iss") + private String companyName; + @JsonProperty("exp") + private Long expirationDateInSeconds; + + public JwtPayload() { + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public JwtUser getUser() { + return user; + } + + public void setUser(JwtUser user) { + this.user = user; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getCompanyName() { + return companyName; + } + + public void setCompanyName(String companyName) { + this.companyName = companyName; + } + + public Long getExpirationDateInSeconds() { + return expirationDateInSeconds; + } + + public void setExpirationDateInSeconds(Long expirationDateInSeconds) { + this.expirationDateInSeconds = expirationDateInSeconds; + } +} diff --git a/src/main/java/authentication/jwt/JwtUser.java b/src/main/java/authentication/jwt/JwtUser.java new file mode 100644 index 000000000..14ac4c8a0 --- /dev/null +++ b/src/main/java/authentication/jwt/JwtUser.java @@ -0,0 +1,118 @@ +package authentication.jwt; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class JwtUser { + private String id; + private String emailAddress; + private String username; + private String firstName; + private String lastName; + private String displayName; + private String title; + private String company; + private String companyId; + private String location; + private String avatarUrl; + private String avatarSmallUrl; + + public JwtUser() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getCompanyId() { + return companyId; + } + + public void setCompanyId(String companyId) { + this.companyId = companyId; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getAvatarSmallUrl() { + return avatarSmallUrl; + } + + public void setAvatarSmallUrl(String avatarSmallUrl) { + this.avatarSmallUrl = avatarSmallUrl; + } +} diff --git a/src/main/java/configuration/SymConfig.java b/src/main/java/configuration/SymConfig.java index a76d2bec9..c94dc7757 100755 --- a/src/main/java/configuration/SymConfig.java +++ b/src/main/java/configuration/SymConfig.java @@ -40,6 +40,7 @@ public class SymConfig { private String appId; private int datafeedEventsThreadpoolSize; private int datafeedEventsErrorTimeout; + private String authenticationFilterUrlPattern; public String getSessionAuthHost() { return sessionAuthHost; @@ -320,4 +321,13 @@ public int getDatafeedEventsErrorTimeout() { public void setDatafeedEventsErrorTimeout(int datafeedEventsErrorTimeout) { this.datafeedEventsErrorTimeout = datafeedEventsErrorTimeout; } + + public String getAuthenticationFilterUrlPattern() { + return authenticationFilterUrlPattern; + } + + public void setAuthenticationFilterUrlPattern(String authenticationFilterUrlPattern) { + this.authenticationFilterUrlPattern = authenticationFilterUrlPattern; + } + } From b2230b1a5ba636b0bd1c79b78b070cedd80b2af4 Mon Sep 17 00:00:00 2001 From: emilioweber Date: Wed, 17 Apr 2019 15:24:11 -0300 Subject: [PATCH 2/2] Removing unnecessary code --- README.md | 4 ++-- .../authentication/SymExtensionAppRSAAuth.java | 18 +++++++----------- .../jwt/AuthenticationFilter.java | 1 - 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3636a13cd..77888bbc4 100755 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ botAuth.authenticate(); ## Request Filter Provides a filter component to validate requests based on their JWT. To enable this feature, it's required that you instantiate the AuthenticationFilter (see example below) and a URL pattern which the filter will be applied to. ``` -AuthenticationFilter = new AuthenticationFilter(symExtensionAppRSAAuth, symConfig); +AuthenticationFilter filter = new AuthenticationFilter(symExtensionAppRSAAuth, symConfig); ``` ## Example main class @@ -246,4 +246,4 @@ InputStream lbConfigStream = getClass().getResourceAsStream("/lb-config.json"); SymLoadBalancedConfig lbConfig = SymConfigLoader.loadLoadBalancer(lbConfigStream); ... SymBotClient botClient = SymBotClient.initBot(config, botAuth, lbConfig); -``` \ No newline at end of file +``` diff --git a/src/main/java/authentication/SymExtensionAppRSAAuth.java b/src/main/java/authentication/SymExtensionAppRSAAuth.java index 11ffc2c83..05959d7f1 100644 --- a/src/main/java/authentication/SymExtensionAppRSAAuth.java +++ b/src/main/java/authentication/SymExtensionAppRSAAuth.java @@ -51,10 +51,6 @@ public final class SymExtensionAppRSAAuth extends APIClient { private TokensRepository tokensRepository; private String podCertificate; - public PublicKey getPodPublicKey() throws CertificateException { - return readPublicKey(); - } - public SymExtensionAppRSAAuth(final SymConfig configuration) { this.config = configuration; ClientBuilder clientBuilder = @@ -182,12 +178,12 @@ public Object verifyJWT(final String jwt) { return null; } - private PublicKey readPublicKey() throws CertificateException { - String encoded = podCertificate.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", ""); - byte[] decoded = Base64.decodeBase64(encoded); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate - x509Certificate = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(decoded)); - return x509Certificate.getPublicKey(); + public PublicKey getPodPublicKey() throws CertificateException { + String encoded = podCertificate.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", ""); + byte[] decoded = Base64.decodeBase64(encoded); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate + x509Certificate = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(decoded)); + return x509Certificate.getPublicKey(); } } diff --git a/src/main/java/authentication/jwt/AuthenticationFilter.java b/src/main/java/authentication/jwt/AuthenticationFilter.java index 54445f556..e0092bb6e 100644 --- a/src/main/java/authentication/jwt/AuthenticationFilter.java +++ b/src/main/java/authentication/jwt/AuthenticationFilter.java @@ -34,7 +34,6 @@ public class AuthenticationFilter implements Filter { private static final String UNAUTHORIZED_JWT_MESSAGE = "Unauthorized JWT"; private static final String INTERNAL_SERVER_ERROR_MESSAGE = "Unexpected error, please contact the system administrator"; - private static final String USER_FIELDNAME = "user"; private static final String USER_ID_FIELDNAME = "sub"; private static final String ANY_FOLLOWING_CHARACTER_PATTERN = ".*";