You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2020/08/28 14:51:08 UTC

[knox] branch master updated: KNOX-2413 - Added JWT support in HadoopAuth provider (#367)

This is an automated email from the ASF dual-hosted git repository.

smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 01d47c5  KNOX-2413 - Added JWT support in HadoopAuth provider (#367)
01d47c5 is described below

commit 01d47c5b34a96b4ebea5e585b1b6d409117d0b88
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Fri Aug 28 16:50:57 2020 +0200

    KNOX-2413 - Added JWT support in HadoopAuth provider (#367)
    
    * KNOX-2413 - Added JWT support in HadoopAuth provider
    
    * KNOX-2413 - Continue using JWT if token exists (even if invalid)
---
 gateway-provider-security-hadoopauth/pom.xml       |  4 ++
 .../gateway/hadoopauth/HadoopAuthMessages.java     |  6 +++
 .../hadoopauth/filter/HadoopAuthFilter.java        | 43 ++++++++++++++-
 .../hadoopauth/filter/HadoopAuthPostFilter.java    | 49 +++++++++++++----
 .../hadoopauth/filter/HadoopAuthFilterTest.java    | 62 ++++++++++++++++++++++
 .../federation/jwt/filter/AbstractJWTFilter.java   |  5 ++
 .../federation/jwt/filter/JWTFederationFilter.java | 24 +++++----
 .../org/apache/knox/gateway/GatewayFilter.java     | 17 +++++-
 8 files changed, 187 insertions(+), 23 deletions(-)

diff --git a/gateway-provider-security-hadoopauth/pom.xml b/gateway-provider-security-hadoopauth/pom.xml
index 4e0dbb5..7095886 100755
--- a/gateway-provider-security-hadoopauth/pom.xml
+++ b/gateway-provider-security-hadoopauth/pom.xml
@@ -35,6 +35,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-provider-security-jwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
             <artifactId>gateway-server</artifactId>
         </dependency>
         <dependency>
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
index c7953c4..23c858f 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
@@ -35,4 +35,10 @@ public interface HadoopAuthMessages {
 
   @Message( level = MessageLevel.DEBUG, text = "Proxy user Authentication failed: {0}" )
   void hadoopAuthProxyUserFailed(@StackTrace Throwable t);
+
+  @Message( level = MessageLevel.DEBUG, text = "Initialized the JWT federation filter" )
+  void initializedJwtFilter();
+
+  @Message( level = MessageLevel.DEBUG, text = "Using JWT filter to serve the request" )
+  void useJwtFilter();
 }
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
index b3a82e1..2ab789e 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
@@ -22,10 +22,12 @@ import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.authorize.AuthorizationException;
 import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.util.HttpExceptionUtils;
+import org.apache.knox.gateway.GatewayFilter;
 import org.apache.knox.gateway.GatewayServer;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.hadoopauth.HadoopAuthMessages;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
 import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.security.AliasService;
@@ -44,6 +46,8 @@ import java.util.Set;
 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.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
@@ -72,10 +76,13 @@ public class HadoopAuthFilter extends
 
   private static final String QUERY_PARAMETER_DOAS = "doAs";
   private static final String PROXYUSER_PREFIX = "hadoop.proxyuser";
+  static final String SUPPORT_JWT = "support.jwt";
+  static final String JWT_PREFIX = "jwt.";
 
   private static final HadoopAuthMessages LOG = MessagesFactory.get(HadoopAuthMessages.class);
 
   private final Set<String> ignoreDoAs = new HashSet<>();
+  private JWTFederationFilter jwtFilter;
 
   @Override
   protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException {
@@ -116,11 +123,34 @@ public class HadoopAuthFilter extends
     }
 
     super.init(filterConfig);
+
+    final String supportJwt = filterConfig.getInitParameter(SUPPORT_JWT);
+    final boolean jwtSupported = Boolean.parseBoolean(supportJwt == null ? "false" : supportJwt);
+    if (jwtSupported) {
+      jwtFilter = new JWTFederationFilter();
+      ((GatewayFilter.Holder)filterConfig).removeParamPrefix(JWT_PREFIX);
+      jwtFilter.init(filterConfig);
+      LOG.initializedJwtFilter();
+    }
   }
 
   @Override
-  protected void doFilter(FilterChain filterChain, HttpServletRequest request,
-                          HttpServletResponse response) throws IOException, ServletException {
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+    if (shouldUseJwtFilter(jwtFilter, (HttpServletRequest) request)) {
+      LOG.useJwtFilter();
+      jwtFilter.doFilter(request, response, filterChain);
+    } else {
+      super.doFilter(request, response, filterChain);
+    }
+  }
+
+  @Override
+  protected void doFilter(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+    if (shouldUseJwtFilter(jwtFilter, request)) {
+      LOG.useJwtFilter();
+      jwtFilter.doFilter(request, response, filterChain);
+      return;
+    }
 
     /*
      * If impersonation is not ignored for the authenticated user, attempt to set a proxied user if
@@ -174,6 +204,11 @@ public class HadoopAuthFilter extends
     super.doFilter(filterChain, request, response);
   }
 
+  static boolean shouldUseJwtFilter(JWTFederationFilter jwtFilter, HttpServletRequest request)
+      throws IOException, ServletException {
+    return jwtFilter == null ? false : jwtFilter.getWireToken(request) != null;
+  }
+
   /**
    * Tests if the authenticated user/service has impersonation enabled based on previously calculated
    * data (see {@link #init(FilterConfig)}.
@@ -244,4 +279,8 @@ public class HadoopAuthFilter extends
     }
     return props;
   }
+
+  boolean isJwtSupported() {
+    return jwtFilter != null;
+  }
 }
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java
index 1913c54..2579083 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java
@@ -17,9 +17,15 @@
  */
 package org.apache.knox.gateway.hadoopauth.filter;
 
+import static org.apache.knox.gateway.hadoopauth.filter.HadoopAuthFilter.JWT_PREFIX;
+import static org.apache.knox.gateway.hadoopauth.filter.HadoopAuthFilter.SUPPORT_JWT;
+import static org.apache.knox.gateway.hadoopauth.filter.HadoopAuthFilter.shouldUseJwtFilter;
+
 import java.io.IOException;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
+import java.text.ParseException;
+import java.util.stream.Collectors;
 
 import javax.security.auth.Subject;
 import javax.servlet.Filter;
@@ -39,6 +45,8 @@ import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
 import org.apache.knox.gateway.filter.AbstractGatewayFilter;
 import org.apache.knox.gateway.hadoopauth.HadoopAuthMessages;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
+import org.apache.knox.gateway.GatewayFilter;
 import org.apache.knox.gateway.audit.api.Action;
 import org.apache.knox.gateway.audit.api.ActionOutcome;
 import org.apache.knox.gateway.audit.api.Auditor;
@@ -51,8 +59,17 @@ public class HadoopAuthPostFilter implements Filter {
       AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
       AuditConstants.KNOX_COMPONENT_NAME );
 
+  private JWTFederationFilter jwtFilter;
+
   @Override
   public void init( FilterConfig filterConfig ) throws ServletException {
+    final String supportJwt = filterConfig.getInitParameter(SUPPORT_JWT);
+    final boolean jwtSupported = Boolean.parseBoolean(supportJwt == null ? "false" : supportJwt);
+    if (jwtSupported) {
+      jwtFilter = new JWTFederationFilter();
+      ((GatewayFilter.Holder)filterConfig).removeParamPrefix(JWT_PREFIX);
+      jwtFilter.init(filterConfig);
+    }
   }
 
   @Override
@@ -60,23 +77,37 @@ public class HadoopAuthPostFilter implements Filter {
   }
 
   @Override
-  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
-      throws IOException, ServletException {
-    HttpServletRequest httpRequest = (HttpServletRequest)request;
-    String principal = httpRequest.getRemoteUser();
-    if (principal != null) {
-        Subject subject = new Subject();
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+    Subject subject = null;
+    if (shouldUseJwtFilter(jwtFilter, (HttpServletRequest) request)) {
+      try {
+        subject = jwtFilter.createSubjectFromToken(jwtFilter.getWireToken(request));
+      } catch (ParseException e) {
+        // NOP: subject remains null -> SC_FORBIDDEN will be returned
+      }
+    } else {
+      final String principal = ((HttpServletRequest) request).getRemoteUser();
+      if (principal != null) {
+        subject = new Subject();
         subject.getPrincipals().add(new PrimaryPrincipal(principal));
-        log.hadoopAuthAssertedPrincipal(principal);
-        auditService.getContext().setUsername( principal ); //KM: Audit Fix
+      }
+    }
+
+    if (subject != null) {
+        log.hadoopAuthAssertedPrincipal(getPrincipalsAsString(subject));
+        auditService.getContext().setUsername(getPrincipalsAsString(subject)); //KM: Audit Fix
         String sourceUri = (String)request.getAttribute( AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME );
         auditor.audit( Action.AUTHENTICATION , sourceUri, ResourceType.URI, ActionOutcome.SUCCESS );
-        doAs(httpRequest, response, chain, subject);
+        doAs(request, response, chain, subject);
     } else {
       ((HttpServletResponse)response).sendError(HttpServletResponse.SC_FORBIDDEN, "User not authenticated");
     }
   }
 
+  private String getPrincipalsAsString(Subject subject) {
+    return String.join(",", subject.getPrincipals().stream().map(principal -> principal.getName()).collect(Collectors.toSet()));
+  }
+
   private void doAs(final ServletRequest request, final ServletResponse response, final FilterChain chain, Subject subject)
       throws IOException, ServletException {
     try {
diff --git a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
index e9a6ef6..8c92d03 100644
--- a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
+++ b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
@@ -22,6 +22,7 @@ import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.createMockBuilder;
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.getCurrentArguments;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
@@ -29,7 +30,11 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import org.apache.knox.gateway.GatewayFilter;
 import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter;
+import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
+import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.topology.Topology;
 import org.junit.Test;
@@ -98,6 +103,7 @@ public class HadoopAuthFilterTest {
     expect(filterConfig.getInitParameter(GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS))
         .andReturn("Knox, hdfs,TesT") // Spacing and case set on purpose
         .atLeastOnce();
+    expect(filterConfig.getInitParameter("support.jwt")).andReturn("false").anyTimes();
     expect(filterConfig.getServletContext()).andReturn(servletContext).atLeastOnce();
 
     Properties configProperties = createMock(Properties.class);
@@ -135,4 +141,60 @@ public class HadoopAuthFilterTest {
 
     verify(filterConfig, configProperties, hadoopAuthFilter, servletContext);
   }
+
+  @Test
+  public void shouldNotUseJwtFilterIfProviderParamIsNotSet() throws Exception {
+    testIfJwtSupported(null);
+  }
+
+  @Test
+  public void shouldNotUseJwtFilterIfProviderParamIsFalse() throws Exception {
+    testIfJwtSupported("false");
+  }
+
+  @Test
+  public void shouldUseJwtFilterIfProviderParamIsTrue() throws Exception {
+    testIfJwtSupported("true");
+  }
+
+  private HadoopAuthFilter testIfJwtSupported(String supportJwt) throws Exception {
+    final GatewayFilter.Holder filterConfig = createMock(GatewayFilter.Holder.class);
+    expect(filterConfig.getInitParameterNames()).andReturn(Collections.enumeration(Collections.emptyList()));
+    expect(filterConfig.getInitParameter(GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS)).andReturn("service").atLeastOnce();
+    expect(filterConfig.getInitParameter("config.prefix")).andReturn("some.prefix").atLeastOnce();
+    expect(filterConfig.getInitParameter("support.jwt")).andReturn(supportJwt).anyTimes();
+    final boolean isJwtSupported = Boolean.parseBoolean(supportJwt);
+    if (isJwtSupported) {
+      filterConfig.removeParamPrefix("jwt.");
+      expectLastCall();
+      expect(filterConfig.getInitParameter(JWTFederationFilter.KNOX_TOKEN_AUDIENCES)).andReturn(null).anyTimes();
+      expect(filterConfig.getInitParameter(JWTFederationFilter.KNOX_TOKEN_QUERY_PARAM_NAME)).andReturn(null).anyTimes();
+      expect(filterConfig.getInitParameter(JWTFederationFilter.JWKS_URL)).andReturn(null).anyTimes();
+      expect(filterConfig.getInitParameter(JWTFederationFilter.TOKEN_PRINCIPAL_CLAIM)).andReturn(null).anyTimes();
+      expect(filterConfig.getInitParameter(JWTFederationFilter.TOKEN_VERIFICATION_PEM)).andReturn(null).anyTimes();
+      expect(filterConfig.getInitParameter(AbstractJWTFilter.JWT_EXPECTED_ISSUER)).andReturn(null).anyTimes();
+      expect(filterConfig.getInitParameter(AbstractJWTFilter.JWT_EXPECTED_SIGALG)).andReturn(null).anyTimes();
+    }
+
+    final ServletContext servletContext = createMock(ServletContext.class);
+    expect(servletContext.getAttribute("signer.secret.provider.object")).andReturn(null).atLeastOnce();
+    if (isJwtSupported) {
+      expect(servletContext.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(null).anyTimes();
+    }
+    expect(filterConfig.getServletContext()).andReturn(servletContext).atLeastOnce();
+
+    final HadoopAuthFilter hadoopAuthFilter = createMockBuilder(HadoopAuthFilter.class).addMockedMethod("getConfiguration", String.class, FilterConfig.class).withConstructor()
+        .createMock();
+    final Properties config = new Properties();
+    config.put("type", "simple");
+    expect(hadoopAuthFilter.getConfiguration(eq("some.prefix."), eq(filterConfig))).andReturn(config).atLeastOnce();
+
+    replay(servletContext, filterConfig, hadoopAuthFilter);
+
+    hadoopAuthFilter.init(filterConfig);
+
+    assertEquals(isJwtSupported, hadoopAuthFilter.isJwtSupported());
+
+    return hadoopAuthFilter;
+  }
 }
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index 1aeea20..dc222bf 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -61,6 +61,7 @@ import org.apache.knox.gateway.services.security.token.TokenStateService;
 import org.apache.knox.gateway.services.security.token.TokenUtils;
 import org.apache.knox.gateway.services.security.token.UnknownTokenException;
 import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
 
 import com.nimbusds.jose.JWSHeader;
 
@@ -229,6 +230,10 @@ public abstract class AbstractJWTFilter implements Filter {
     }
   }
 
+  public Subject createSubjectFromToken(String token) throws ParseException {
+    return createSubjectFromToken(new JWTToken(token));
+  }
+
   protected Subject createSubjectFromToken(JWT token) {
     String principal = token.getSubject();
     String claimvalue = null;
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index e4a7a25..4a7d425 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -37,7 +37,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
 
   public static final String KNOX_TOKEN_AUDIENCES = "knox.token.audiences";
   public static final String TOKEN_VERIFICATION_PEM = "knox.token.verification.pem";
-  private static final String KNOX_TOKEN_QUERY_PARAM_NAME = "knox.token.query.param.name";
+  public static final String KNOX_TOKEN_QUERY_PARAM_NAME = "knox.token.query.param.name";
   public static final String TOKEN_PRINCIPAL_CLAIM = "knox.token.principal.claim";
   public static final String JWKS_URL = "knox.token.jwks.url";
   private static final String BEARER = "Bearer ";
@@ -85,16 +85,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
       throws IOException, ServletException {
-    String header = ((HttpServletRequest) request).getHeader("Authorization");
-    String wireToken;
-    if (header != null && header.startsWith(BEARER)) {
-      // what follows the bearer designator should be the JWT token being used to request or as an access token
-      wireToken = header.substring(BEARER.length());
-    }
-    else {
-      // check for query param
-      wireToken = request.getParameter(paramName);
-    }
+    final String wireToken = getWireToken(request);
 
     if (wireToken != null) {
       try {
@@ -113,6 +104,17 @@ public class JWTFederationFilter extends AbstractJWTFilter {
     }
   }
 
+  public String getWireToken(ServletRequest request) {
+    final String header = ((HttpServletRequest) request).getHeader("Authorization");
+    if (header != null && header.startsWith(BEARER)) {
+      // what follows the bearer designator should be the JWT token being used to request or as an access token
+      return header.substring(BEARER.length());
+    } else {
+      // check for query param
+      return request.getParameter(paramName);
+    }
+  }
+
   @Override
   protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
                                        String error) throws IOException {
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java
index 64a0f75..a1012d0 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java
@@ -280,7 +280,7 @@ public class GatewayFilter implements Filter {
     }
   }
 
-  private class Holder implements Filter, FilterConfig {
+  public class Holder implements Filter, FilterConfig {
     private Template template;
     private String name;
     private Map<String,String> params;
@@ -328,6 +328,21 @@ public class GatewayFilter implements Filter {
       return value;
     }
 
+    public void removeParamPrefix(String prefix) {
+      if (params != null) {
+        final Set<String> relevantParams = new HashSet<>();
+        params.entrySet().forEach(paramEntry -> {
+          if (paramEntry.getKey().startsWith(prefix)) {
+            relevantParams.add(paramEntry.getKey());
+          }
+        });
+        relevantParams.forEach(param -> {
+          String value = params.remove(param);
+          params.put(param.replace(prefix, ""), value);
+        });
+      }
+    }
+
     @Override
     public Enumeration<String> getInitParameterNames() {
       Enumeration<String> names = null;