Skip to content

Commit

Permalink
Merge pull request #49 from emilioweber/ext_app_rsa_auth
Browse files Browse the repository at this point in the history
Adding request filter
  • Loading branch information
ronerjr authored Apr 17, 2019
2 parents 13645aa + b2230b1 commit 035e513
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 2 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
}
```

Expand Down Expand Up @@ -115,6 +118,12 @@ ClientConfig kmAuthClientConfig = new ClientConfig();
SymBotAuth botAuth = new SymBotAuth(config, sessionAuthClientConfig, kmAuthClientConfig);
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 filter = new AuthenticationFilter(symExtensionAppRSAAuth, symConfig);
```

## Example main class
```java
Expand Down Expand Up @@ -237,4 +246,4 @@ InputStream lbConfigStream = getClass().getResourceAsStream("/lb-config.json");
SymLoadBalancedConfig lbConfig = SymConfigLoader.loadLoadBalancer(lbConfigStream);
...
SymBotClient botClient = SymBotClient.initBot(config, botAuth, lbConfig);
```
```
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@
<artifactId>jose4j</artifactId>
<version>0.6.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>

<distributionManagement>
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/authentication/SymExtensionAppRSAAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -171,4 +177,13 @@ public Object verifyJWT(final String jwt) {
}
return null;
}

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();
}
}
106 changes: 106 additions & 0 deletions src/main/java/authentication/jwt/AuthenticationFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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_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<Claims> 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);
}
}
60 changes: 60 additions & 0 deletions src/main/java/authentication/jwt/JwtPayload.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
118 changes: 118 additions & 0 deletions src/main/java/authentication/jwt/JwtUser.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions src/main/java/configuration/SymConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class SymConfig {
private String appId;
private int datafeedEventsThreadpoolSize;
private int datafeedEventsErrorTimeout;
private String authenticationFilterUrlPattern;

public String getSessionAuthHost() {
return sessionAuthHost;
Expand Down Expand Up @@ -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;
}

}

0 comments on commit 035e513

Please sign in to comment.