From e1da84ec5528f8d23cff65272054df3a0f0065a0 Mon Sep 17 00:00:00 2001 From: harshanl Date: Tue, 1 Sep 2015 22:48:35 +0530 Subject: [PATCH] Added JWT authenticator --- .../pom.xml | 45 +++++- .../authenticator/framework/DataHolder.java | 23 ++- .../framework/WebappAuthenticatorFactory.java | 11 +- .../authenticator/JWTAuthenticator.java | 140 ++++++++++++++++++ .../authenticator/OAuthAuthenticator.java | 8 +- ...thenticatorFrameworkServiceComponent.java} | 39 +++-- pom.xml | 8 + 7 files changed, 244 insertions(+), 30 deletions(-) create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java rename components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/internal/{WebappAuthenticatorFrameworkBundleActivator.java => WebappAuthenticatorFrameworkServiceComponent.java} (70%) diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml index ebf27bf99ff..34051486391 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml @@ -37,6 +37,10 @@ + + org.apache.felix + maven-scr-plugin + org.apache.felix maven-bundle-plugin @@ -45,16 +49,47 @@ ${project.artifactId} ${project.artifactId} - org.wso2.carbon.webapp.authenticator.framework.internal.WebappAuthenticatorFrameworkBundleActivator - org.wso2.carbon.webapp.authenticator.framework.internal - * !org.wso2.carbon.webapp.authenticator.framework.internal, org.wso2.carbon.webapp.authenticator.framework.* + + com.nimbusds.jose, + com.nimbusds.jose.crypto, + com.nimbusds.jwt, + javax.xml.bind, + javax.xml.bind.annotation, + javax.xml.parsers, + javax.xml.validation, + org.apache.axiom.util.base64, + org.apache.catalina, + org.apache.catalina.connector, + org.apache.catalina.util, + org.apache.commons.logging, + org.apache.coyote, + org.apache.tomcat.util.buf, + org.apache.tomcat.util.http, + org.osgi.service.component, + org.w3c.dom, + org.wso2.carbon.apimgt.api, + org.wso2.carbon.apimgt.core.authenticate, + org.wso2.carbon.apimgt.core.gateway, + org.wso2.carbon.apimgt.impl.dto, + org.wso2.carbon.context, + org.wso2.carbon.core.util, + org.wso2.carbon.identity.base, + org.wso2.carbon.identity.core.util, + org.wso2.carbon.tomcat.ext.valves, + org.wso2.carbon.user.api, + org.wso2.carbon.user.core.service, + org.wso2.carbon.user.core.tenant, + org.wso2.carbon.utils, + org.wso2.carbon.utils.multitenancy, + org.xml.sax + @@ -103,6 +138,10 @@ org.wso2.carbon org.wso2.carbon.core.services + + org.wso2.orbit.com.nimbusds + nimbus-jose-jwt + diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/DataHolder.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/DataHolder.java index ca36310fbf0..12be00a5703 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/DataHolder.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/DataHolder.java @@ -18,19 +18,34 @@ */ package org.wso2.carbon.webapp.authenticator.framework; +import org.wso2.carbon.user.core.service.RealmService; + public class DataHolder { - private static WebappAuthenticatorRepository repository; + private WebappAuthenticatorRepository repository; + private RealmService realmService; private DataHolder() {} - public static void setWebappAuthenticatorRepository (WebappAuthenticatorRepository repository) { - DataHolder.repository = repository; + private static DataHolder thisInstance = new DataHolder(); + + public static DataHolder getInstance() { + return thisInstance; + } + + public void setWebappAuthenticatorRepository (WebappAuthenticatorRepository repository) { + this.repository = repository; } - public static WebappAuthenticatorRepository getWebappAuthenticatorRepository() { + public WebappAuthenticatorRepository getWebappAuthenticatorRepository() { return repository; } + public RealmService getRealmService() { + return realmService; + } + public void setRealmService(RealmService realmService) { + this.realmService = realmService; + } } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/WebappAuthenticatorFactory.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/WebappAuthenticatorFactory.java index 5885a0e8fba..18758a468bf 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/WebappAuthenticatorFactory.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/WebappAuthenticatorFactory.java @@ -18,18 +18,9 @@ */ package org.wso2.carbon.webapp.authenticator.framework; -import org.apache.catalina.connector.Request; -import org.apache.catalina.util.Base64; -import org.apache.tomcat.util.buf.ByteChunk; -import org.apache.tomcat.util.buf.CharChunk; -import org.apache.tomcat.util.buf.MessageBytes; -import org.wso2.carbon.webapp.authenticator.framework.authenticator.BasicAuthAuthenticator; -import org.wso2.carbon.webapp.authenticator.framework.authenticator.OAuthAuthenticator; - public class WebappAuthenticatorFactory { public static WebappAuthenticator getAuthenticator(String authScheme) { - return DataHolder.getWebappAuthenticatorRepository().getAuthenticator(authScheme); + return DataHolder.getInstance().getWebappAuthenticatorRepository().getAuthenticator(authScheme); } - } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java new file mode 100644 index 00000000000..20651681f94 --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * you may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.webapp.authenticator.framework.authenticator; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jwt.SignedJWT; +import org.apache.axiom.util.base64.Base64Utils; +import org.apache.axis2.transport.http.HTTPConstants; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.core.util.KeyStoreManager; +import org.wso2.carbon.user.api.TenantManager; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.api.UserStoreManager; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; +import org.wso2.carbon.webapp.authenticator.framework.DataHolder; +import org.wso2.carbon.webapp.authenticator.framework.WebappAuthenticator; + +import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; +import java.util.StringTokenizer; + +/** + * This authenticator authenticates HTTP requests using JWT header. + */ +public class JWTAuthenticator implements WebappAuthenticator { + + private static final Log log = LogFactory.getLog(JWTAuthenticator.class); + public static final String SIGNED_JWT_AUTH_USERNAME = "Username"; + private static final String JWT_AUTHENTICATOR = "JWT"; + + @Override + public boolean isAuthenticated(Request request) { + return false; + } + + @Override + public Status authenticate(Request request, Response response) { + String requestUri = request.getRequestURI(); + if (requestUri == null || "".equals(requestUri)) { + return Status.CONTINUE; + } + StringTokenizer tokenizer = new StringTokenizer(requestUri, "/"); + String context = tokenizer.nextToken(); + if (context == null || "".equals(context)) { + return Status.CONTINUE; + } + + if (log.isDebugEnabled()) { + log.debug("Authenticating using JWT header."); + } + + //Get the filesystem keystore default primary certificate + KeyStoreManager keyStoreManager = KeyStoreManager.getInstance( + MultitenantConstants.SUPER_TENANT_ID); + try { + keyStoreManager.getDefaultPrimaryCertificate(); + String authorizationHeader = request.getHeader(HTTPConstants.HEADER_AUTHORIZATION); + String headerData = decodeAuthorizationHeader(authorizationHeader); + JWSVerifier verifier = + new RSASSAVerifier((RSAPublicKey) keyStoreManager.getDefaultPublicKey()); + SignedJWT jwsObject = SignedJWT.parse(headerData); + if (jwsObject.verify(verifier)) { + String username = jwsObject.getJWTClaimsSet().getStringClaim(SIGNED_JWT_AUTH_USERNAME); + String tenantDomain = MultitenantUtils.getTenantDomain(username); + username = MultitenantUtils.getTenantAwareUsername(username); + TenantManager tenantManager = DataHolder.getInstance().getRealmService().getTenantManager(); + int tenantId = tenantManager.getTenantId(tenantDomain); + + if (tenantId == -1) { + log.error("tenantDomain is not valid. username : " + username + ", tenantDomain " + + ": " + tenantDomain); + return Status.FAILURE; + } + + UserStoreManager userStore = DataHolder.getInstance().getRealmService(). + getTenantUserRealm(tenantId).getUserStoreManager(); + if (userStore.isExistingUser(username)) { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantId(tenantId); + ctx.setUsername(username); + return Status.SUCCESS; + } + } + } catch (UserStoreException e) { + log.error("Error occurred while obtaining the user.", e); + return Status.FAILURE; + } catch (ParseException e) { + log.error("Error occurred while parsing the JWT header.", e); + return Status.FAILURE; + } catch (JOSEException e) { + log.error("Error occurred while verifying the JWT header.", e); + return Status.FAILURE; + } catch (Exception e) { + log.error("Error occurred while verifying the JWT header.", e); + return Status.FAILURE; + } + return Status.CONTINUE; + } + + private String decodeAuthorizationHeader(String authorizationHeader) { + String[] splitValues = authorizationHeader.trim().split(" "); + byte[] decodedBytes = Base64Utils.decode(splitValues[1].trim()); + if (decodedBytes != null) { + return new String(decodedBytes); + } else { + if (log.isDebugEnabled()) { + log.debug("Error decoding authorization header."); + } + return null; + } + } + + @Override + public String getName() { + return JWTAuthenticator.JWT_AUTHENTICATOR; + } +} diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/OAuthAuthenticator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/OAuthAuthenticator.java index 3bd7b54e34e..cf5a177167d 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/OAuthAuthenticator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/OAuthAuthenticator.java @@ -69,12 +69,13 @@ public class OAuthAuthenticator implements WebappAuthenticator { try { if (Constants.NO_MATCHING_AUTH_SCHEME.equals(authLevel)) { AuthenticationFrameworkUtil.handleNoMatchAuthScheme(request, response, request.getMethod(), - apiVersion, context); + apiVersion, context); return Status.CONTINUE; } else { String bearerToken = this.getBearerToken(request); boolean isAuthenticated = - AuthenticationFrameworkUtil.doAuthenticate(context, apiVersion, bearerToken, authLevel, domain); + AuthenticationFrameworkUtil.doAuthenticate(context, apiVersion, + bearerToken, authLevel, domain); return (isAuthenticated) ? Status.SUCCESS : Status.FAILURE; } } catch (APIManagementException e) { @@ -94,7 +95,8 @@ public class OAuthAuthenticator implements WebappAuthenticator { private String getBearerToken(Request request) { MessageBytes authorization = - request.getCoyoteRequest().getMimeHeaders().getValue(Constants.HTTPHeaders.HEADER_HTTP_AUTHORIZATION); + request.getCoyoteRequest().getMimeHeaders(). + getValue(Constants.HTTPHeaders.HEADER_HTTP_AUTHORIZATION); String tokenValue = null; if (authorization != null) { diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkBundleActivator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkServiceComponent.java similarity index 70% rename from components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkBundleActivator.java rename to components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkServiceComponent.java index cb3ab4ac837..6dad1613d39 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkBundleActivator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkServiceComponent.java @@ -20,10 +20,10 @@ package org.wso2.carbon.webapp.authenticator.framework.internal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; import org.wso2.carbon.tomcat.ext.valves.CarbonTomcatValve; import org.wso2.carbon.tomcat.ext.valves.TomcatValveContainer; +import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.webapp.authenticator.framework.DataHolder; import org.wso2.carbon.webapp.authenticator.framework.WebappAuthenticator; import org.wso2.carbon.webapp.authenticator.framework.WebappAuthenticatorFrameworkValve; @@ -34,25 +34,34 @@ import org.wso2.carbon.webapp.authenticator.framework.config.WebappAuthenticator import java.util.ArrayList; import java.util.List; -public class WebappAuthenticatorFrameworkBundleActivator implements BundleActivator { - private static final Log log = LogFactory.getLog(WebappAuthenticatorFrameworkBundleActivator.class); +/** + * @scr.component name="org.wso2.carbon.webapp.authenticator" immediate="true" + * @scr.reference name="user.realmservice.default" + * interface="org.wso2.carbon.user.core.service.RealmService" + * cardinality="1..1" + * policy="dynamic" + * bind="setRealmService" + * unbind="unsetRealmService" + */ +public class WebappAuthenticatorFrameworkServiceComponent { + + private static final Log log = LogFactory.getLog(WebappAuthenticatorFrameworkServiceComponent.class); - @Override - public void start(BundleContext bundleContext) throws Exception { + @SuppressWarnings("unused") + protected void activate(ComponentContext componentContext) { if (log.isDebugEnabled()) { log.debug("Starting Web Application Authenticator Framework Bundle"); } try { WebappAuthenticatorConfig.init(); - WebappAuthenticatorRepository repository = new WebappAuthenticatorRepository(); for (AuthenticatorConfig config : WebappAuthenticatorConfig.getInstance().getAuthenticators()) { WebappAuthenticator authenticator = (WebappAuthenticator) Class.forName(config.getClassName()).newInstance(); repository.addAuthenticator(authenticator); } - DataHolder.setWebappAuthenticatorRepository(repository); + DataHolder.getInstance().setWebappAuthenticatorRepository(repository); List valves = new ArrayList(); valves.add(new WebappAuthenticatorFrameworkValve()); @@ -66,9 +75,19 @@ public class WebappAuthenticatorFrameworkBundleActivator implements BundleActiva } } - @Override - public void stop(BundleContext bundleContext) throws Exception { + @SuppressWarnings("unused") + protected void deactivate(ComponentContext componentContext) { //do nothing } + protected void setRealmService(RealmService realmService) { + if (log.isDebugEnabled()) { + log.debug("RealmService acquired"); + } + DataHolder.getInstance().setRealmService(realmService); + } + + protected void unsetRealmService(RealmService realmService) { + DataHolder.getInstance().setRealmService(null); + } } diff --git a/pom.xml b/pom.xml index 2bf3fd8ac97..a3f508df4f8 100644 --- a/pom.xml +++ b/pom.xml @@ -558,6 +558,11 @@ axis2 ${axis2.orbit.version} + + org.wso2.orbit.com.nimbusds + nimbus-jose-jwt + ${nimbus.orbit.version} + @@ -1251,6 +1256,9 @@ 2.0.2.wso2v2 + + + 2.26.1.wso2v3