killbill-memoizeit

Changes

pom.xml 5(+3 -2)

util/pom.xml 4(+4 -0)

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
index f25423a..1142ba6 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -57,6 +57,8 @@ import org.killbill.billing.util.glue.NonEntityDaoModule;
 import org.killbill.billing.util.glue.RecordIdModule;
 import org.killbill.billing.util.glue.SecurityModule;
 import org.killbill.billing.util.glue.TagStoreModule;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
+import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
 
 public class BeatrixIntegrationModule extends KillBillModule {
 
@@ -95,7 +97,7 @@ public class BeatrixIntegrationModule extends KillBillModule {
         install(new RecordIdModule(configSource));
         install(new UsageModule(configSource));
         install(new SecurityModule(configSource));
-        install(new KillBillShiroModule(configSource));
+        install(new KillBillShiroModuleOnlyIniRealm(configSource));
         install(new BeatrixModule(configSource));
 
         bind(AccountChecker.class).asEagerSingleton();
@@ -104,7 +106,6 @@ public class BeatrixIntegrationModule extends KillBillModule {
         bind(PaymentChecker.class).asEagerSingleton();
         bind(RefundChecker.class).asEagerSingleton();
         bind(AuditChecker.class).asEagerSingleton();
-
         bind(TestApiListener.class).asEagerSingleton();
     }
 
@@ -130,4 +131,16 @@ public class BeatrixIntegrationModule extends KillBillModule {
             install(new MockPaymentProviderPluginModule(NON_OSGI_PLUGIN_NAME, TestIntegrationBase.getClock(), configSource));
         }
     }
+
+    private static class KillBillShiroModuleOnlyIniRealm extends KillBillShiroModule {
+
+        public KillBillShiroModuleOnlyIniRealm(final KillbillConfigSource configSource) {
+            super(configSource);
+        }
+        protected void configureJDBCRealm() {
+        }
+        protected void configureLDAPRealm() {
+        }
+
+    }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RoleDefinitionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RoleDefinitionJson.java
new file mode 100644
index 0000000..1f4c8a6
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RoleDefinitionJson.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.jaxrs.json;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RoleDefinitionJson {
+
+    private final String role;
+    private final List<String> permissions;
+
+    @JsonCreator
+    public RoleDefinitionJson(@JsonProperty("role") final String role,
+                              @JsonProperty("permissions") final List<String> permissions) {
+        this.role = role;
+        this.permissions = permissions;
+    }
+
+    public String getRole() {
+        return role;
+    }
+
+    public List<String> getPermissions() {
+        return permissions;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UserRolesJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UserRolesJson.java
new file mode 100644
index 0000000..af6ab2f
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UserRolesJson.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.jaxrs.json;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UserRolesJson {
+
+    private final String username;
+    private final String password;
+    private final List<String> roles;
+
+    @JsonCreator
+    public UserRolesJson(@JsonProperty("username") final String username,
+                         @JsonProperty("password") final String password,
+                         @JsonProperty("roles") final List<String> roles) {
+        this.username = username;
+        this.password = password;
+        this.roles = roles;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public List<String> getRoles() {
+        return roles;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
index 5e78159..66d1b43 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
@@ -79,7 +79,7 @@ public class PluginResource extends JaxRsResourceBase {
     private final HttpServlet osgiServlet;
 
     @Inject
-    public PluginResource(@Named("osgi") final HttpServlet osgiServlet,
+    public PluginResource(@Named("osgi") final HttpServlet osgiServlet, // See DefaultOSGIModule.OSGI_NAMED
                           final JaxrsUriBuilder uriBuilder,
                           final TagUserApi tagUserApi,
                           final CustomFieldUserApi customFieldUserApi,
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
index 0330c54..33f734f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
@@ -21,27 +21,36 @@ import java.util.Set;
 
 import javax.inject.Singleton;
 import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
 
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.subject.Subject;
-
 import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.billing.payment.api.PaymentApi;
-import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.clock.Clock;
+import org.killbill.billing.jaxrs.json.RoleDefinitionJson;
 import org.killbill.billing.jaxrs.json.SubjectJson;
+import org.killbill.billing.jaxrs.json.UserRolesJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.security.Permission;
+import org.killbill.billing.security.SecurityApiException;
 import org.killbill.billing.security.api.SecurityApi;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
 
 import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Functions;
@@ -100,4 +109,89 @@ public class SecurityResource extends JaxRsResourceBase {
         final SubjectJson subjectJson = new SubjectJson(subject);
         return Response.status(Status.OK).entity(subjectJson).build();
     }
+
+    @Timed
+    @POST
+    @Path("/users")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Add a new user with roles (to make api requests)")
+    public Response addUserRoles(final UserRolesJson json,
+                                 @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                 @HeaderParam(HDR_REASON) final String reason,
+                                 @HeaderParam(HDR_COMMENT) final String comment,
+                                 @javax.ws.rs.core.Context final HttpServletRequest request,
+                                 @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+        securityApi.addUserRoles(json.getUsername(), json.getPassword(), json.getRoles(), context.createContext(createdBy, reason, comment, request));
+        return Response.status(Status.CREATED).build();
+    }
+
+    @Timed
+    @PUT
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @Path("/users/{username:" + ANYTHING_PATTERN + "}/password")
+    @ApiOperation(value = "Update a user password")
+    public Response updateUserPassword(final UserRolesJson json,
+                                       @PathParam("username") final String username,
+                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                       @HeaderParam(HDR_REASON) final String reason,
+                                       @HeaderParam(HDR_COMMENT) final String comment,
+                                       @javax.ws.rs.core.Context final HttpServletRequest request,
+                                       @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+        securityApi.updateUserPassword(username, json.getPassword(), context.createContext(createdBy, reason, comment, request));
+        return Response.status(Status.OK).build();
+    }
+
+
+    @Timed
+    @PUT
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @Path("/users/{username:" + ANYTHING_PATTERN + "}/roles")
+    @ApiOperation(value = "Update roles associated to a user")
+    public Response updateUserRoles(final UserRolesJson json,
+                                    @PathParam("username") final String username,
+                                    @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                    @HeaderParam(HDR_REASON) final String reason,
+                                    @HeaderParam(HDR_COMMENT) final String comment,
+                                    @javax.ws.rs.core.Context final HttpServletRequest request,
+                                    @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+        securityApi.updateUserRoles(username, json.getRoles(), context.createContext(createdBy, reason, comment, request));
+        return Response.status(Status.OK).build();
+    }
+
+    @Timed
+    @DELETE
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @Path("/users/{username:" + ANYTHING_PATTERN + "}")
+    @ApiOperation(value = "Invalidate an existing user")
+    public Response invalidateUser(@PathParam("username") final String username,
+                                    @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                    @HeaderParam(HDR_REASON) final String reason,
+                                    @HeaderParam(HDR_COMMENT) final String comment,
+                                    @javax.ws.rs.core.Context final HttpServletRequest request,
+                                    @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+        securityApi.invalidateUser(username, context.createContext(createdBy, reason, comment, request));
+        return Response.status(Status.NO_CONTENT).build();
+    }
+
+
+
+    @Timed
+    @POST
+    @Path("/roles")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Add a new role definition)")
+    public Response addRoleDefinition(final RoleDefinitionJson json,
+                                      @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                      @HeaderParam(HDR_REASON) final String reason,
+                                      @HeaderParam(HDR_COMMENT) final String comment,
+                                      @javax.ws.rs.core.Context final HttpServletRequest request,
+                                      @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+        securityApi.addRoleDefinition(json.getRole(), json.getPermissions(), context.createContext(createdBy, reason, comment, request));
+        return Response.status(Status.CREATED).build();
+    }
 }

pom.xml 5(+3 -2)

diff --git a/pom.xml b/pom.xml
index 544dcd9..84a6e30 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Copyright 2010-2013 Ning, Inc.
-  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014-2015 Groupon, Inc
+  ~ Copyright 2014-2015 The Billing Project, LLC
   ~
-  ~ Groupon licenses this file to you under the Apache License, version 2.0
+  ~ The Billing Project 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:
   ~
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index 113ed01..0617b12 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -36,6 +36,7 @@ import org.killbill.billing.util.glue.IniRealmProvider;
 import org.killbill.billing.util.glue.JDBCSessionDaoProvider;
 import org.killbill.billing.util.glue.KillBillShiroModule;
 import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
 import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
@@ -61,6 +62,8 @@ public class KillBillShiroWebModule extends ShiroWebModule {
 
         bindRealm().toProvider(IniRealmProvider.class).asEagerSingleton();
 
+        bindRealm().to(KillBillJdbcRealm.class).asEagerSingleton();
+
         if (KillBillShiroModule.isLDAPEnabled()) {
             bindRealm().to(KillBillJndiLdapRealm.class).asEagerSingleton();
         }
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
index 242de3c..4fb769e 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
@@ -40,7 +40,7 @@ import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
 import org.apache.shiro.realm.Realm;
 import org.killbill.billing.jaxrs.resources.JaxrsResource;
 import org.killbill.billing.server.listeners.KillbillGuiceListener;
-import org.killbill.billing.server.modules.KillbillServerModule;
+import org.killbill.billing.server.modules.KillbillPlatformModule;
 import org.killbill.billing.tenant.api.Tenant;
 import org.killbill.billing.tenant.api.TenantApiException;
 import org.killbill.billing.tenant.api.TenantUserApi;
@@ -61,17 +61,17 @@ public class TenantFilter implements Filter {
     protected TenantUserApi tenantUserApi;
 
     @Inject
-    @Named(KillbillServerModule.SHIRO_DATA_SOURCE_ID)
+    @Named(KillbillPlatformModule.SHIRO_DATA_SOURCE_ID_NAMED)
     protected DataSource dataSource;
 
     private ModularRealmAuthenticator modularRealmAuthenticator;
 
     @Override
     public void init(final FilterConfig filterConfig) throws ServletException {
-        final Realm killbillJdbcRealm = new KillbillJdbcRealm(dataSource);
+        final Realm killbillJdbcTenantRealm = new KillbillJdbcTenantRealm(dataSource);
         // We use Shiro to verify the api credentials - but the Shiro Subject is only used for RBAC
         modularRealmAuthenticator = new ModularRealmAuthenticator();
-        modularRealmAuthenticator.setRealms(ImmutableList.<Realm>of(killbillJdbcRealm));
+        modularRealmAuthenticator.setRealms(ImmutableList.<Realm>of(killbillJdbcTenantRealm));
     }
 
     @Override
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java
index e3fb516..b7d67d8 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java
@@ -18,6 +18,7 @@
 
 package org.killbill.billing.jaxrs;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 
@@ -25,11 +26,18 @@ import javax.annotation.Nullable;
 import javax.ws.rs.core.Response.Status;
 
 import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Permissions;
+import org.killbill.billing.client.model.RoleDefinition;
+import org.killbill.billing.client.model.UserRoles;
 import org.killbill.billing.security.Permission;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.ning.http.client.Response;
+
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
 
 public class TestSecurity extends TestJaxrsBase {
 
@@ -55,6 +63,146 @@ public class TestSecurity extends TestJaxrsBase {
         Assert.assertEquals(new HashSet<String>(stephanesPermissions), ImmutableSet.<String>of(Permission.PAYMENT_CAN_REFUND.toString()));
     }
 
+    @Test(groups = "slow")
+    public void testDynamicUserRolesAllPermissions() throws Exception {
+        testDynamicUserRolesInternal("wqeqwe", "jdsh763s", "all", "*", true);
+    }
+
+    @Test(groups = "slow")
+    public void testDynamicUserRolesAllCatalogPermissions() throws Exception {
+        testDynamicUserRolesInternal("wqeqsdswe", "jsddsh763s", "allcatalog", "catalog:*", true);
+    }
+
+    @Test(groups = "slow")
+    public void testDynamicUserRolesCorrectCatalogPermissions() throws Exception {
+        testDynamicUserRolesInternal("wqeq23f6we", "jds5gh763s", "correctcatalog", "catalog:config_upload", true);
+    }
+
+    @Test(groups = "slow")
+    public void testDynamicUserRolesIncorrectPermissions() throws Exception {
+        testDynamicUserRolesInternal("wqsdeqwe", "jd23fsh63s", "incorrect", "account:*", false);
+    }
+
+    @Test(groups = "slow")
+    public void testUserPermission() throws KillBillClientException {
+
+        final String roleDefinition = "notEnoughToAddUserAndRoles";
+
+        final List<String> permissions = new ArrayList<String>();
+        for (Permission cur : Permission.values()) {
+            if (!cur.getGroup().equals("user")) {
+                permissions.add(cur.toString());
+            }
+        }
+        Response response = killBillClient.addRoleDefinition(new RoleDefinition(roleDefinition, permissions), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 201);
+
+        final String username = "candy";
+        final String password = "lolipop";
+        response = killBillClient.addUserRoles(new UserRoles(username, password, ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 201);
+
+        // Now 'login' as new user (along with roles to make an API call requiring permissions), and check behavior
+        logout();
+        login(username, password);
+
+        boolean success = false;
+        try {
+            killBillClient.addRoleDefinition(new RoleDefinition("dsfdsfds", ImmutableList.of("*")), createdBy, reason, comment);
+            success = true;
+        } catch (final Exception e) {
+        } finally {
+            Assert.assertFalse(success);
+        }
+
+        success = false;
+        try {
+            killBillClient.addUserRoles(new UserRoles("sdsd", "sdsdsd", ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+            success = true;
+        } catch (final Exception e) {
+        } finally {
+            Assert.assertFalse(success);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testUserWithUpdates() throws KillBillClientException {
+
+        final String roleDefinition = "somethingNice";
+        final String allPermissions = "*";
+
+        final String username = "GuanYu";
+        final String password = "IamAGreatWarrior";
+
+        Response response = killBillClient.addRoleDefinition(new RoleDefinition(roleDefinition, ImmutableList.of(allPermissions)), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 201);
+
+        response = killBillClient.addUserRoles(new UserRoles(username, password, ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 201);
+
+        logout();
+        login(username, password);
+        Permissions permissions =  killBillClient.getPermissions();
+        Assert.assertEquals(permissions.size(), Permission.values().length);
+
+        String newPassword = "IamTheBestWarrior";
+        killBillClient.updateUserPassword(username, newPassword, createdBy, reason, comment);
+
+        logout();
+        login(username, newPassword);
+        permissions =  killBillClient.getPermissions();
+        Assert.assertEquals(permissions.size(), Permission.values().length);
+
+        final String newRoleDefinition = "somethingLessNice";
+        // Only enough permissions to invalidate itself in the last step...
+        final String littlePermissions = "user";
+
+        response = killBillClient.addRoleDefinition(new RoleDefinition(newRoleDefinition, ImmutableList.of(littlePermissions)), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 201);
+
+        killBillClient.updateUserRoles(username, ImmutableList.of(newRoleDefinition), createdBy, reason, comment);
+        permissions =  killBillClient.getPermissions();
+        // This will only work if correct shiro cache invalidation was performed... requires lots of sweat to get it to work ;-)
+        Assert.assertEquals(permissions.size(), 2);
+
+        killBillClient.invalidateUser(username, createdBy, reason, comment);
+        try {
+            killBillClient.getPermissions();
+            Assert.fail();
+        } catch (final KillBillClientException e) {
+            Assert.assertEquals(e.getResponse().getStatusCode(), Status.UNAUTHORIZED.getStatusCode());
+        }
+
+
+    }
+
+    private void testDynamicUserRolesInternal(final String username, final String password, final String roleDefinition, final String permissions, final boolean expectPermissionSuccess) throws Exception {
+
+        Response response = killBillClient.addRoleDefinition(new RoleDefinition(roleDefinition, ImmutableList.of(permissions)), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 201);
+
+        response = killBillClient.addUserRoles(new UserRoles(username, password, ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 201);
+
+        // Now 'login' as new user (along with roles to make an API call requiring permissions), and check behavior
+        logout();
+        login(username, password);
+
+        boolean success = false;
+        try {
+            final String catalogPath = Resources.getResource("SpyCarBasic.xml").getPath();
+            killBillClient.uploadXMLCatalog(catalogPath, createdBy, reason, comment);
+            success = true;
+        } catch (final Exception e) {
+            if (expectPermissionSuccess ||
+                !e.getMessage().startsWith("java.lang.IllegalArgumentException: Unauthorized")) {
+                throw e;
+            }
+        } finally {
+            Assert.assertTrue(success == expectPermissionSuccess);
+        }
+    }
+
     private List<String> getPermissions(@Nullable final String username, @Nullable final String password) throws Exception {
         login(username, password);
         return killBillClient.getPermissions();
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
index fae6f7f..a401574 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
@@ -33,7 +33,7 @@ import org.skife.jdbi.v2.IDBI;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.tenant.api.Tenant;
 import org.killbill.billing.tenant.api.TenantApiException;
-import org.killbill.billing.tenant.security.KillbillCredentialsMatcher;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java b/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java
index ddba113..d23e86a 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java
@@ -23,12 +23,11 @@ import org.apache.shiro.authc.AuthenticationInfo;
 import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import org.killbill.billing.tenant.TenantTestSuiteWithEmbeddedDb;
 import org.killbill.billing.tenant.api.DefaultTenant;
-import org.killbill.billing.tenant.security.KillbillCredentialsMatcher;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
 
 public class TestDefaultTenantDao extends TenantTestSuiteWithEmbeddedDb {
 

util/pom.xml 4(+4 -0)

diff --git a/util/pom.xml b/util/pom.xml
index 6bb4fc1..eb59185 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -133,6 +133,10 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-platform-osgi-api</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
index cb1c9f0..379e3b6 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
@@ -33,7 +33,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 // Really Provider<IniRealm>, but avoid an extra cast below
-public class IniRealmProvider implements Provider<Realm> {
+public class IniRealmProvider implements Provider<IniRealm> {
 
     private static final Logger log = LoggerFactory.getLogger(IniRealmProvider.class);
 
@@ -45,7 +45,7 @@ public class IniRealmProvider implements Provider<Realm> {
     }
 
     @Override
-    public Realm get() {
+    public IniRealm get() {
         try {
             final Factory<SecurityManager> factory = new IniSecurityManagerFactory(securityConfig.getShiroResourcePath());
             // TODO Pierre hack - lame cast here, but we need to have Shiro go through its reflection magic
@@ -53,8 +53,17 @@ public class IniRealmProvider implements Provider<Realm> {
             // by going through IniSecurityManagerFactory.
             final DefaultSecurityManager securityManager = (DefaultSecurityManager) factory.getInstance();
             final Collection<Realm> realms = securityManager.getRealms();
-            // Null check mainly for testing
-            return realms == null ? new IniRealm(securityConfig.getShiroResourcePath()) : realms.iterator().next();
+            if (realms == null || realms.isEmpty()) {
+                return new IniRealm(securityConfig.getShiroResourcePath());
+            }
+
+            for (final Realm cur : realms) {
+                if (cur instanceof IniRealm) {
+                    return (IniRealm) cur;
+                }
+            }
+            throw new ConfigurationException();
+
         } catch (final ConfigurationException e) {
             log.warn("Unable to configure RBAC", e);
             return new IniRealm();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index f74ffa0..00551eb 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -26,6 +26,7 @@ import org.apache.shiro.session.mgt.SessionManager;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.RbacConfig;
 import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
 import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
@@ -39,6 +40,7 @@ public class KillBillShiroModule extends ShiroModule {
     public static final String KILLBILL_LDAP_PROPERTY = "killbill.server.ldap";
     public static final String KILLBILL_RBAC_PROPERTY = "killbill.server.rbac";
 
+
     public static boolean isLDAPEnabled() {
         return Boolean.parseBoolean(System.getProperty(KILLBILL_LDAP_PROPERTY, "false"));
     }
@@ -64,6 +66,16 @@ public class KillBillShiroModule extends ShiroModule {
 
         bindRealm().toProvider(IniRealmProvider.class).asEagerSingleton();
 
+        configureJDBCRealm();
+
+        configureLDAPRealm();
+    }
+
+    protected void configureJDBCRealm() {
+        bindRealm().to(KillBillJdbcRealm.class).asEagerSingleton();
+    }
+
+    protected void configureLDAPRealm() {
         if (isLDAPEnabled()) {
             bindRealm().to(KillBillJndiLdapRealm.class).asEagerSingleton();
         }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
index 7ad1e93..1f0920c 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
@@ -24,6 +24,8 @@ import org.killbill.billing.util.config.SecurityConfig;
 import org.killbill.billing.util.security.api.DefaultSecurityApi;
 import org.killbill.billing.util.security.api.DefaultSecurityService;
 import org.killbill.billing.util.security.api.SecurityService;
+import org.killbill.billing.util.security.shiro.dao.DefaultUserDao;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
 import org.skife.config.ConfigurationObjectFactory;
 
 public class SecurityModule extends KillBillModule {
@@ -34,10 +36,15 @@ public class SecurityModule extends KillBillModule {
 
     public void configure() {
         installConfig();
+        installDao();
         installSecurityApi();
         installSecurityService();
     }
 
+    protected void installDao() {
+        bind(UserDao.class).to(DefaultUserDao.class).asEagerSingleton();
+    }
+
     private void installConfig() {
         final SecurityConfig securityConfig = new ConfigurationObjectFactory(skifeConfigSource).build(SecurityConfig.class);
         bind(SecurityConfig.class).toInstance(securityConfig);
diff --git a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
index 5d0bc2b..f1718c1 100644
--- a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
+++ b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
@@ -16,29 +16,56 @@
 
 package org.killbill.billing.util.security.api;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.realm.jdbc.JdbcRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
 import org.apache.shiro.subject.Subject;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.security.Logical;
 import org.killbill.billing.security.Permission;
 import org.killbill.billing.security.SecurityApiException;
 import org.killbill.billing.security.api.SecurityApi;
+import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.security.shiro.dao.RolesPermissionsModelDao;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
+import org.killbill.billing.util.security.shiro.dao.UserRolesModelDao;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
 
+import com.google.common.base.Function;
 import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
 public class DefaultSecurityApi implements SecurityApi {
 
     private static final String[] allPermissions = new String[Permission.values().length];
 
+    private final UserDao userDao;
+
+    @Inject
+    public DefaultSecurityApi(final UserDao userDao) {
+        this.userDao = userDao;
+    }
+
     @Override
     public synchronized void login(final Object principal, final Object credentials) {
         final Subject currentUser = SecurityUtils.getSubject();
@@ -124,6 +151,118 @@ public class DefaultSecurityApi implements SecurityApi {
         }
     }
 
+    @Override
+    public void addUserRoles(final String username, final String password, final List<String> roles, final CallContext callContext) throws SecurityApiException {
+        userDao.insertUser(username, password, roles, callContext.getUserName());
+    }
+
+    @Override
+    public void updateUserPassword(final String username, final String password, final CallContext callContext) throws SecurityApiException {
+        userDao.updateUserPassword(username, password, callContext.getUserName());
+    }
+
+    @Override
+    public void updateUserRoles(final String username, final List<String> roles, final CallContext callContext) throws SecurityApiException {
+        userDao.updateUserRoles(username, roles, callContext.getUserName());
+        invalidateJDBCAuthorizationCache(username);
+    }
+
+
+    @Override
+    public void invalidateUser(final String username, final CallContext callContext) throws SecurityApiException {
+        userDao.invalidateUser(username, callContext.getUserName());
+    }
+
+    @Override
+    public List<String> getUserRoles(final String username, final TenantContext tenantContext) {
+        final List<UserRolesModelDao> permissionsModelDao = userDao.getUserRoles(username);
+        return ImmutableList.copyOf(Iterables.transform(permissionsModelDao, new Function<UserRolesModelDao, String>() {
+            @Nullable
+            @Override
+            public String apply(final UserRolesModelDao input) {
+                return input.getRoleName();
+            }
+        }));
+    }
+
+    @Override
+    public void addRoleDefinition(final String role, final List<String> permissions, final CallContext callContext) throws SecurityApiException {
+        final List<String> sanitizedPermissions = sanitizeAndValidatePermissions(permissions);
+        userDao.addRoleDefinition(role, sanitizedPermissions, callContext.getUserName());
+    }
+
+    @Override
+    public List<String> getRoleDefinition(final String role, final TenantContext tenantContext) {
+        final List<RolesPermissionsModelDao> permissionsModelDao = userDao.getRoleDefinition(role);
+        return ImmutableList.copyOf(Iterables.transform(permissionsModelDao, new Function<RolesPermissionsModelDao, String>() {
+            @Nullable
+            @Override
+            public String apply(final RolesPermissionsModelDao input) {
+                return input.getPermission();
+            }
+        }));
+    }
+
+    private List<String> sanitizeAndValidatePermissions(final List<String> permissions) throws SecurityApiException {
+
+        if (permissions == null || permissions.isEmpty()) {
+            throw new SecurityApiException(ErrorCode.SECURITY_INVALID_PERMISSIONS, "null");
+        }
+
+        final HashMap<String, Set<String>> groupToValues = new HashMap<String, Set<String>>();
+        for (final String curPerm : permissions) {
+
+            if (curPerm.equals("*")) {
+                return ImmutableList.of("*");
+            }
+
+            final String[] permissionParts = curPerm.split(":");
+            if (permissionParts.length != 1 && permissionParts.length != 2) {
+                throw new SecurityApiException(ErrorCode.SECURITY_INVALID_PERMISSIONS, curPerm);
+            }
+
+            boolean resolved = false;
+            for (final Permission cur : Permission.values()) {
+                if (resolved) {
+                    break;
+                }
+                if (!cur.getGroup().equals(permissionParts[0])) {
+                    continue;
+                }
+
+                Set<String> groupPermissions = groupToValues.get(permissionParts[0]);
+                if (groupPermissions == null) {
+                    groupPermissions = new HashSet<String>();
+                    groupToValues.put(permissionParts[0], groupPermissions);
+                }
+                if (permissionParts.length == 1 || permissionParts[1].equals("*")) {
+                    groupPermissions.clear();
+                    groupPermissions.add("*");
+                    resolved = true;
+                    break;
+                }
+
+                if (cur.getValue().equals(permissionParts[1])) {
+                    groupPermissions.add(permissionParts[1]);
+                    resolved = true;
+                    break;
+                }
+            }
+            if (!resolved) {
+                throw new SecurityApiException(ErrorCode.SECURITY_INVALID_PERMISSIONS, curPerm);
+            }
+        }
+
+        final List<String> sanitizedPermissions = new ArrayList<String>();
+        for (String group : groupToValues.keySet()) {
+            final Set<String> groupPermissions = groupToValues.get(group);
+            for (String value : groupPermissions) {
+                sanitizedPermissions.add(String.format("%s:%s", group, value));
+            }
+        }
+        return sanitizedPermissions;
+    }
+
     private String[] getAllPermissionsAsStrings() {
         if (allPermissions[0] == null) {
             synchronized (allPermissions) {
@@ -135,7 +274,22 @@ public class DefaultSecurityApi implements SecurityApi {
                 }
             }
         }
-
         return allPermissions;
     }
+
+    private void invalidateJDBCAuthorizationCache(final String username) {
+        final Collection<Realm> realms = ((DefaultSecurityManager) SecurityUtils.getSecurityManager()).getRealms();
+        final KillBillJdbcRealm killBillJdbcRealm = (KillBillJdbcRealm) Iterables.tryFind(realms, new Predicate<Realm>() {
+            @Override
+            public boolean apply(@Nullable final Realm input) {
+                return (input instanceof KillBillJdbcRealm);
+            }
+        }).orNull();
+
+        if (killBillJdbcRealm != null) {
+            SimplePrincipalCollection principals = new SimplePrincipalCollection();
+            principals.add(username, killBillJdbcRealm.getName());
+            killBillJdbcRealm.clearCachedAuthorizationInfo(principals);
+        }
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
new file mode 100644
index 0000000..92cc4f9
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.shiro.crypto.RandomNumberGenerator;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.hash.SimpleHash;
+import org.apache.shiro.util.ByteSource;
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.security.SecurityApiException;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
+import org.killbill.clock.Clock;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+public class DefaultUserDao implements UserDao {
+
+    private static final RandomNumberGenerator rng = new SecureRandomNumberGenerator();
+    private final IDBI dbi;
+    private final Clock clock;
+
+    @Inject
+    public DefaultUserDao(final IDBI dbi, final Clock clock) {
+        this.dbi = dbi;
+        this.clock = clock;
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserModelDao.class));
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserRolesModelDao.class));
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(RolesPermissionsModelDao.class));
+
+    }
+
+    @Override
+    public void insertUser(final String username, final String password, final List<String> roles, final String createdBy) throws SecurityApiException {
+
+        final ByteSource salt = rng.nextBytes();
+        final String hashedPasswordBase64 = new SimpleHash(KillbillCredentialsMatcher.HASH_ALGORITHM_NAME,
+                                                           password, salt.toBase64(), KillbillCredentialsMatcher.HASH_ITERATIONS).toBase64();
+
+        final DateTime createdDate = clock.getUTCNow();
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
+                for (String role : roles) {
+                    final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
+                    final List<RolesPermissionsModelDao> currentRolePermissions = rolesPermissionsSqlDao.getByRoleName(role);
+                    if (currentRolePermissions.isEmpty()) {
+                        throw new SecurityApiException(ErrorCode.SECURITY_INVALID_ROLE, role);
+                    }
+                    userRolesSqlDao.create(new UserRolesModelDao(username, role, createdDate, createdBy));
+                }
+
+                final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+                final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+                if (userModelDao != null) {
+                    throw new SecurityApiException(ErrorCode.SECURITY_USER_ALREADY_EXISTS, username);
+                }
+                usersSqlDao.create(new UserModelDao(username, hashedPasswordBase64, salt.toBase64(), createdDate, createdBy));
+                return null;
+            }
+        });
+    }
+
+    public List<UserRolesModelDao> getUserRoles(final String username) {
+        return dbi.inTransaction(new TransactionCallback<List<UserRolesModelDao>>() {
+            @Override
+            public List<UserRolesModelDao> inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
+                return userRolesSqlDao.getByUsername(username);
+            }
+        });
+
+    }
+
+    @Override
+    public void addRoleDefinition(final String role, final List<String> permissions, final String createdBy) {
+        final DateTime createdDate = clock.getUTCNow();
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+
+                final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
+                final List<RolesPermissionsModelDao> existingRole  = rolesPermissionsSqlDao.getByRoleName(role);
+                if (!existingRole.isEmpty()) {
+                    throw new SecurityApiException(ErrorCode.SECURITY_ROLE_ALREADY_EXISTS, role);
+                }
+                for (String permission : permissions) {
+                    rolesPermissionsSqlDao.create(new RolesPermissionsModelDao(role, permission, createdDate, createdBy));
+                }
+                return null;
+            }
+        });
+
+    }
+
+    @Override
+    public List<RolesPermissionsModelDao> getRoleDefinition(final String role) {
+        return dbi.inTransaction(new TransactionCallback<List<RolesPermissionsModelDao>>() {
+            @Override
+            public List<RolesPermissionsModelDao> inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
+                return rolesPermissionsSqlDao.getByRoleName(role);
+            }
+        });
+    }
+
+    @Override
+    public void updateUserPassword(final String username, final String password, final String updatedBy) throws SecurityApiException {
+
+        final ByteSource salt = rng.nextBytes();
+        final String hashedPasswordBase64 = new SimpleHash(KillbillCredentialsMatcher.HASH_ALGORITHM_NAME,
+                                                           password, salt.toBase64(), KillbillCredentialsMatcher.HASH_ITERATIONS).toBase64();
+
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+
+                final DateTime updatedDate = clock.getUTCNow();
+                final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+                final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+                if (userModelDao == null) {
+                    throw new SecurityApiException(ErrorCode.SECURITY_INVALID_USER, username);
+                }
+                usersSqlDao.updatePassword(username, hashedPasswordBase64, salt.toBase64(), updatedDate.toDate(), updatedBy);
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void updateUserRoles(final String username, final List<String> roles, final String updatedBy) throws SecurityApiException {
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final DateTime updatedDate = clock.getUTCNow();
+                final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+                final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+                if (userModelDao == null) {
+                    throw new SecurityApiException(ErrorCode.SECURITY_INVALID_USER, username);
+                }
+
+                // Remove stale entries
+                final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
+                final List<UserRolesModelDao> existingRoles = userRolesSqlDao.getByUsername(username);
+                for (final UserRolesModelDao curRole : existingRoles) {
+                    if (Iterables.tryFind(roles, new Predicate<String>() {
+                        @Override
+                        public boolean apply(final String input) {
+                            return input.equals(curRole.getRoleName());
+                        }
+                    }).orNull() == null) {
+                        userRolesSqlDao.invalidate(username, curRole.getRoleName(), updatedDate.toDate(), updatedBy);
+                    }
+                }
+
+                // Add new entries
+                for (final String curNewRole : roles) {
+                    if (Iterables.tryFind(existingRoles, new Predicate<UserRolesModelDao>() {
+                        @Override
+                        public boolean apply(final UserRolesModelDao input) {
+                            return input.getRoleName().equals(curNewRole);
+                        }
+                    }).orNull() == null) {
+                        userRolesSqlDao.create(new UserRolesModelDao(username, curNewRole, updatedDate, updatedBy));
+                    }
+                }
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void invalidateUser(final String username, final String updatedBy) throws SecurityApiException {
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+
+                final DateTime updatedDate = clock.getUTCNow();
+                final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+                final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+                if (userModelDao == null) {
+                    throw new SecurityApiException(ErrorCode.SECURITY_INVALID_USER, username);
+                }
+                usersSqlDao.invalidate(username, updatedDate.toDate(), updatedBy);
+                return null;
+            }
+        });
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsModelDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsModelDao.java
new file mode 100644
index 0000000..bd22436
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsModelDao.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import org.joda.time.DateTime;
+
+public class RolesPermissionsModelDao {
+
+    private Long recordId;
+    private String roleName;
+    private String permission;
+    private Boolean isActive;
+    private DateTime createdDate;
+    private String createdBy;
+    private DateTime updatedDate;
+    private String updatedBy;
+
+    public RolesPermissionsModelDao() {
+    }
+
+    public RolesPermissionsModelDao(final Long recordId, final String roleName, final String permission, final Boolean is_active, final DateTime createdDate, final String createdBy, final DateTime updatedDate, final String updatedBy) {
+        this.recordId = recordId;
+        this.roleName = roleName;
+        this.permission = permission;
+        this.isActive = is_active;
+        this.createdDate = createdDate;
+        this.createdBy = createdBy;
+        this.updatedDate = updatedDate;
+        this.updatedBy = updatedBy;
+    }
+
+    public RolesPermissionsModelDao(final String roleName, final String permission, final DateTime createdDate, final String createdBy) {
+        this(-1L, roleName, permission, Boolean.TRUE, createdDate, createdBy, createdDate, createdBy);
+    }
+
+        public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(final String roleName) {
+        this.roleName = roleName;
+    }
+
+    public String getPermission() {
+        return permission;
+    }
+
+    public void setPermission(final String permission) {
+        this.permission = permission;
+    }
+
+    public Boolean getIsActive() {
+        return isActive;
+    }
+
+    public void setIsActive(final Boolean isActive) {
+        this.isActive = isActive;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(final String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    public void setUpdatedDate(final DateTime updatedDate) {
+        this.updatedDate = updatedDate;
+    }
+
+    public String getUpdatedBy() {
+        return updatedBy;
+    }
+
+    public void setUpdatedBy(final String updatedBy) {
+        this.updatedBy = updatedBy;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
new file mode 100644
index 0000000..0e25943
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import java.util.List;
+
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface RolesPermissionsSqlDao extends Transactional<RolesPermissionsSqlDao> {
+
+    @SqlQuery
+    public RolesPermissionsModelDao getByRecordId(@Bind("recordId") final Long recordId);
+
+    @SqlQuery
+    public List<RolesPermissionsModelDao> getByRoleName(@Bind("roleName") final String roleName);
+
+    @SqlUpdate
+    public void create(@SmartBindBean final RolesPermissionsModelDao rolesPermissions);
+
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
new file mode 100644
index 0000000..bb0ab0d
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import java.util.List;
+
+import org.killbill.billing.security.SecurityApiException;
+
+public interface UserDao {
+
+    public void insertUser(String username, String password, List<String> roles, String createdBy) throws SecurityApiException;
+
+    public List<UserRolesModelDao> getUserRoles(String username);
+
+    public void addRoleDefinition(String role, List<String> permissions, String createdBy);
+
+    public List<RolesPermissionsModelDao> getRoleDefinition(String role);
+
+    public void updateUserPassword(String username, String password, String createdBy) throws SecurityApiException;
+
+    public void updateUserRoles(String username, List<String> roles, String createdBy) throws SecurityApiException;
+
+    public void invalidateUser(String username, String createdBy) throws SecurityApiException;
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserModelDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserModelDao.java
new file mode 100644
index 0000000..88cf250
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserModelDao.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import org.joda.time.DateTime;
+
+public class UserModelDao {
+
+    private Long recordId;
+    private String username;
+    private String password;
+    private String passwordSalt;
+    private Boolean isActive;
+    private DateTime createdDate;
+    private String createdBy;
+    private DateTime updatedDate;
+    private String updatedBy;
+
+    public UserModelDao() {
+    }
+
+    public UserModelDao(final Long recordId, final String username, final String password, final String passwordSalt, final Boolean isActive, final DateTime createdDate, final String createdBy, final DateTime updatedDate, final String updatedBy) {
+        this.recordId = recordId;
+        this.username = username;
+        this.password = password;
+        this.passwordSalt = passwordSalt;
+        this.isActive = isActive;
+        this.createdDate = createdDate;
+        this.createdBy = createdBy;
+        this.updatedDate = updatedDate;
+        this.updatedBy = updatedBy;
+    }
+
+    public UserModelDao(final String username, final String password, final String passwordSalt, final DateTime createdDate, final String createdBy) {
+        this(-1L, username, password, passwordSalt, Boolean.TRUE, createdDate, createdBy, createdDate, createdBy);
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(final String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(final String password) {
+        this.password = password;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(final String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    public void setUpdatedDate(final DateTime updatedDate) {
+        this.updatedDate = updatedDate;
+    }
+
+    public String getUpdatedBy() {
+        return updatedBy;
+    }
+
+    public void setUpdatedBy(final String updatedBy) {
+        this.updatedBy = updatedBy;
+    }
+
+    public String getPasswordSalt() {
+        return passwordSalt;
+    }
+
+    public void setPasswordSalt(final String passwordSalt) {
+        this.passwordSalt = passwordSalt;
+    }
+
+    public Boolean getIsActive() {
+        return isActive;
+    }
+
+    public void setIsActive(final Boolean isActive) {
+        this.isActive = isActive;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesModelDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesModelDao.java
new file mode 100644
index 0000000..4b12cfc
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesModelDao.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import org.joda.time.DateTime;
+
+public class UserRolesModelDao {
+
+    private Long recordId;
+    private String username;
+    private String roleName;
+    private Boolean isActive;
+    private DateTime createdDate;
+    private String createdBy;
+    private DateTime updatedDate;
+    private String updatedBy;
+
+    public UserRolesModelDao() {
+    }
+
+    public UserRolesModelDao(final Long recordId, final String username, final String roleName, final Boolean isActive, final DateTime createdDate, final String createdBy, final DateTime updatedDate, final String updatedBy) {
+        this.recordId = recordId;
+        this.username = username;
+        this.roleName = roleName;
+        this.isActive = isActive;
+        this.createdDate = createdDate;
+        this.createdBy = createdBy;
+        this.updatedDate = updatedDate;
+        this.updatedBy = updatedBy;
+    }
+
+    public UserRolesModelDao(final String username, final String roleName, final DateTime createdDate, final String createdBy) {
+        this(-1L, username, roleName, Boolean.TRUE, createdDate, createdBy, createdDate, createdBy);
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(final String username) {
+        this.username = username;
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(final String roleName) {
+        this.roleName = roleName;
+    }
+
+    public Boolean getIsActive() {
+        return isActive;
+    }
+
+    public void setIsActive(final Boolean isActive) {
+        this.isActive = isActive;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(final String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    public void setUpdatedDate(final DateTime updatedDate) {
+        this.updatedDate = updatedDate;
+    }
+
+    public String getUpdatedBy() {
+        return updatedBy;
+    }
+
+    public void setUpdatedBy(final String updatedBy) {
+        this.updatedBy = updatedBy;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java
new file mode 100644
index 0000000..c59d80c
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import java.util.Date;
+import java.util.List;
+
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface UserRolesSqlDao extends Transactional<UserRolesSqlDao> {
+
+    @SqlQuery
+    public UserRolesModelDao getByRecordId(@Bind("recordId") Long recordId);
+
+    @SqlQuery
+    public List<UserRolesModelDao> getByUsername(@Bind("username") String username);
+
+    @SqlUpdate
+    public void create(@SmartBindBean UserRolesModelDao userRolesModelDao);
+
+    @SqlUpdate
+    public void invalidate(@Bind("username") String username,
+                           @Bind("roleName") String roleName,
+                           @Bind("updatedDate") final Date updatedDate,
+                           @Bind("updatedBy") final String updatedBy);
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java
new file mode 100644
index 0000000..1a7a599
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.dao;
+
+import java.util.Date;
+
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface UsersSqlDao extends Transactional<UsersSqlDao> {
+
+    @SqlQuery
+    public UserModelDao getByRecordId(@Bind("recordId") Long recordId);
+
+    @SqlQuery
+    public UserModelDao getByUsername(@Bind("username") String username);
+
+    @SqlUpdate
+    public void create(@SmartBindBean UserModelDao userModelDao);
+
+    @SqlUpdate
+    public void updatePassword(@Bind("username") String username,
+                               @Bind("password") String password,
+                               @Bind("passwordSalt") String passwordSalt,
+                               @Bind("updatedDate") Date updatedDate,
+                               @Bind("updatedBy") String updatedBy);
+
+    @SqlUpdate
+    public void invalidate(@Bind("username") String username,
+                           @Bind("updatedDate") Date updatedDate,
+                           @Bind("updatedBy") String updatedBy);
+}
\ No newline at end of file
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java
new file mode 100644
index 0000000..9806abe
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.realm;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.sql.DataSource;
+
+import org.apache.shiro.realm.jdbc.JdbcRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.killbill.billing.platform.glue.KillBillPlatformModuleBase;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
+
+public class KillBillJdbcRealm extends JdbcRealm {
+
+    protected static final String KILLBILL_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ? and is_active";
+    protected static final String KILLBILL_USER_ROLES_QUERY = "select role_name from user_roles where username = ? and is_active";
+    protected static final String KILLBILL_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ? and is_active";
+
+    private final DataSource dataSource;
+
+    @Inject
+    public KillBillJdbcRealm(@Named(KillBillPlatformModuleBase.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource) {
+        super();
+        this.dataSource = dataSource;
+
+        // Tweak JdbcRealm defaults
+        setPermissionsLookupEnabled(true);
+        setAuthenticationQuery(KILLBILL_SALTED_AUTHENTICATION_QUERY);
+        setUserRolesQuery(KILLBILL_USER_ROLES_QUERY);
+        setPermissionsQuery(KILLBILL_PERMISSIONS_QUERY);
+
+        configureSecurity();
+        configureDataSource();
+    }
+
+    @Override
+    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
+        super.clearCachedAuthorizationInfo(principals);
+    }
+
+    private void configureSecurity() {
+        setSaltStyle(SaltStyle.COLUMN);
+        setCredentialsMatcher(KillbillCredentialsMatcher.getCredentialsMatcher());
+    }
+
+    private void configureDataSource() {
+        setDataSource(dataSource);
+    }
+}
diff --git a/util/src/main/resources/org/killbill/billing/util/ddl.sql b/util/src/main/resources/org/killbill/billing/util/ddl.sql
index 2704dc9..c1482a5 100644
--- a/util/src/main/resources/org/killbill/billing/util/ddl.sql
+++ b/util/src/main/resources/org/killbill/billing/util/ddl.sql
@@ -238,3 +238,49 @@ create table sessions (
 , session_data mediumblob default null
 , primary key(record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+
+
+DROP TABLE IF EXISTS users;
+CREATE TABLE users (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    username varchar(128) NULL,
+    password varchar(128) NULL,
+    password_salt varchar(128) NULL,
+    is_active bool DEFAULT 1,
+    created_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    updated_date datetime DEFAULT NULL,
+    updated_by varchar(50) DEFAULT NULL,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX users_username ON users(username);
+
+
+DROP TABLE IF EXISTS user_roles;
+CREATE TABLE user_roles (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    username varchar(128) NULL,
+    role_name varchar(128) NULL,
+    is_active bool DEFAULT 1,
+    created_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    updated_date datetime DEFAULT NULL,
+    updated_by varchar(50) DEFAULT NULL,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX user_roles_idx ON user_roles(username, role_name);
+
+
+DROP TABLE IF EXISTS roles_permissions;
+CREATE TABLE roles_permissions (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    role_name varchar(128) NULL,
+    permission varchar(128) NULL,
+    is_active bool DEFAULT 1,
+    created_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    updated_date datetime DEFAULT NULL,
+    updated_by varchar(50) DEFAULT NULL,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX roles_permissions_idx ON roles_permissions(role_name, permission);
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg
new file mode 100644
index 0000000..5da1774
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg
@@ -0,0 +1,63 @@
+group RolesPermissionsSqlDao;
+
+
+tableName() ::= "roles_permissions"
+
+tableFields(prefix) ::= <<
+  <prefix>role_name
+, <prefix>permission
+, <prefix>is_active
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>updated_date
+, <prefix>updated_by
+>>
+
+allTableFields(prefix) ::= <<
+  <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+  :roleName
+, :permission
+, :isActive
+, :createdDate
+, :createdBy
+, :updatedDate
+, :updatedBy
+>>
+
+
+allTableValues() ::= <<
+  :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and is_active
+;
+>>
+
+
+getByRoleName() ::= <<
+select <allTableFields()>
+from <tableName()>
+where role_name = :roleName
+and is_active
+;
+>>
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg
new file mode 100644
index 0000000..48b2a92
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg
@@ -0,0 +1,70 @@
+group UserRolesSqlDao;
+
+tableName() ::= "user_roles"
+
+tableFields(prefix) ::= <<
+  <prefix>username
+, <prefix>role_name
+, <prefix>is_active
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>updated_date
+, <prefix>updated_by
+>>
+
+allTableFields(prefix) ::= <<
+  <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+  :username
+, :roleName
+, :isActive
+, :createdDate
+, :createdBy
+, :updatedDate
+, :updatedBy
+>>
+
+
+allTableValues() ::= <<
+  :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and is_active
+;
+>>
+
+getByUsername() ::= <<
+select <allTableFields()>
+from <tableName()>
+where username = :username
+and is_active
+;
+>>
+
+invalidate() ::= <<
+update <tableName()>
+set is_active = 0
+where
+username = :username
+and role_name = :roleName
+;
+>>
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg
new file mode 100644
index 0000000..75a832d
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg
@@ -0,0 +1,83 @@
+group UsersSqlDao;
+
+tableName() ::= "users"
+
+tableFields(prefix) ::= <<
+  <prefix>username
+, <prefix>password
+, <prefix>password_salt
+, <prefix>is_active
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>updated_date
+, <prefix>updated_by
+>>
+
+allTableFields(prefix) ::= <<
+  <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+  :username
+, :password
+, :passwordSalt
+, :isActive
+, :createdDate
+, :createdBy
+, :updatedDate
+, :updatedBy
+>>
+
+
+allTableValues() ::= <<
+  :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and is_active
+;
+>>
+
+getByUsername() ::= <<
+select <allTableFields()>
+from <tableName()>
+where username = :username
+and is_active
+;
+>>
+
+updatePassword() ::= <<
+update <tableName()>
+set password = :password
+, password_salt = :passwordSalt
+, updated_date = :updatedDate
+, updated_by = :updatedBy
+where username = :username
+and is_active
+;
+>>
+
+invalidate() ::= <<
+update <tableName()>
+set is_active = 0
+where
+username = :username
+;
+>>
+
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestSecurityModuleNoDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestSecurityModuleNoDB.java
new file mode 100644
index 0000000..a3620db
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestSecurityModuleNoDB.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.glue;
+
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
+import org.mockito.Mockito;
+
+public class TestSecurityModuleNoDB extends SecurityModule {
+
+    public TestSecurityModuleNoDB(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
+    @Override
+    protected void installDao() {
+        bind(UserDao.class).toInstance(Mockito.mock(UserDao.class));
+    }
+
+}
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
index bb32ac1..7e34658 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
@@ -48,8 +48,21 @@ public class TestUtilModuleNoDB extends TestUtilModule {
 
         installAuditMock();
 
-        install(new KillBillShiroModule(configSource));
+        install(new ShiroModuleNoDB(configSource));
         install(new KillBillShiroAopModule());
         install(new SecurityModule(configSource));
     }
+
+    public static class ShiroModuleNoDB extends KillBillShiroModule {
+
+        public ShiroModuleNoDB(final KillbillConfigSource configSource) {
+            super(configSource);
+        }
+
+        protected void configureJDBCRealm() {
+        }
+
+        protected void configureLDAPRealm() {
+        }
+    }
 }
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
index 1537ac4..0b56059 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
  * The Billing Project 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
@@ -37,8 +37,19 @@ public class TestUtilModuleWithEmbeddedDB extends TestUtilModule {
         install(new TagStoreModule(configSource));
         install(new CustomFieldModule(configSource));
         install(new NonEntityDaoModule(configSource));
+        install(new SecurityModuleWithNoSecurityManager(configSource));
         install(new GlobalLockerModule(configSource));
-
         bind(TestApiListener.class).asEagerSingleton();
     }
+
+    private final class SecurityModuleWithNoSecurityManager extends SecurityModule {
+
+        public SecurityModuleWithNoSecurityManager(final KillbillConfigSource configSource) {
+            super(configSource);
+        }
+
+        protected void installSecurityService() {
+        }
+
+    }
 }
diff --git a/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java b/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
index 31ef1b2..b83c1fa 100644
--- a/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
+++ b/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
@@ -34,7 +34,7 @@ public class TestDefaultSecurityApi extends UtilTestSuiteNoDB {
         configureShiro();
 
         // We don't want the Guice injected one (it has Shiro disabled)
-        final SecurityApi securityApi = new DefaultSecurityApi();
+        final SecurityApi securityApi = new DefaultSecurityApi(null);
 
         final Set<Permission> anonsPermissions = securityApi.getCurrentUserPermissions(callContext);
         Assert.assertEquals(anonsPermissions.size(), 0);
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java
new file mode 100644
index 0000000..540f375
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.security.shiro.realm;
+
+import java.util.List;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.DelegatingSubject;
+import org.apache.shiro.util.ThreadContext;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.security.Permission;
+import org.killbill.billing.security.SecurityApiException;
+import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class TestKillBillJdbcRealm extends UtilTestSuiteWithEmbeddedDB {
+
+    private SecurityManager securityManager;
+
+    @Override
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        final KillBillJdbcRealm realm = new KillBillJdbcRealm(helper.getDataSource());
+        securityManager = new DefaultSecurityManager(realm);
+        SecurityUtils.setSecurityManager(securityManager);
+    }
+
+    @AfterMethod(groups = "slow")
+    public void afterMethod() throws Exception {
+        super.afterMethod();
+        ThreadContext.unbindSecurityManager();
+
+    }
+
+    @Test(groups = "slow")
+    public void testAuthentication() throws SecurityApiException {
+
+        final String username = "toto";
+        final String password = "supperCompli43cated";
+
+        securityApi.addRoleDefinition("root", ImmutableList.of("*"), callContext);
+        securityApi.addUserRoles(username, password, ImmutableList.of("root"), callContext);
+        final DelegatingSubject subject = new DelegatingSubject(securityManager);
+
+        final AuthenticationToken goodToken = new UsernamePasswordToken(username, password);
+        securityManager.login(subject, goodToken);
+        Assert.assertTrue(true);
+
+        try {
+            final AuthenticationToken badToken = new UsernamePasswordToken(username, "somethingelse");
+            securityManager.login(subject, badToken);
+            Assert.assertTrue(true);
+            securityManager.logout(subject);
+            securityManager.login(subject, badToken);
+            Assert.fail("Should not succeed to login with an incorrect password");
+        } catch (final AuthenticationException e) {
+        }
+
+        // Update password and try again
+        final String newPassword = "suppersimple";
+        securityApi.updateUserPassword(username, newPassword, callContext);
+
+        try {
+            final AuthenticationToken notGoodTokenAnyLonger = goodToken;
+            securityManager.login(subject, notGoodTokenAnyLonger);
+            Assert.fail("Should not succeed to login with an incorrect password");
+        } catch (final AuthenticationException e) {
+        }
+
+
+        final AuthenticationToken newGoodToken = new UsernamePasswordToken(username, newPassword);
+        securityManager.login(subject, newGoodToken);
+        Assert.assertTrue(true);
+
+        securityManager.logout(subject);
+        securityApi.invalidateUser(username, callContext);
+
+        try {
+            final AuthenticationToken notGoodTokenAnyLonger = goodToken;
+            securityManager.login(subject, notGoodTokenAnyLonger);
+            Assert.fail("Should not succeed to login with an incorrect password");
+        } catch (final AuthenticationException e) {
+        }
+
+    }
+
+    @Test(groups = "slow")
+    public void testInvalidPermissions() {
+        testInvalidPermissionScenario(null);
+        testInvalidPermissionScenario(ImmutableList.<String>of());
+        testInvalidPermissionScenario(ImmutableList.of("foo"));
+        testInvalidPermissionScenario(ImmutableList.of("account:garbage"));
+        testInvalidPermissionScenario(ImmutableList.of("tag:delete_tag_definition", "account:hsgdsgdjsgd"));
+        testInvalidPermissionScenario(ImmutableList.of("account:credit:vvvv"));
+    }
+
+    @Test(groups = "slow")
+    public void testSanityOfPermissions() throws SecurityApiException {
+        securityApi.addRoleDefinition("sanity1", ImmutableList.of("account:*", "*"), callContext);
+        validateUserRoles(securityApi.getRoleDefinition("sanity1", callContext), ImmutableList.of("*"));
+
+        securityApi.addRoleDefinition("sanity2", ImmutableList.of("account:charge", "account:charge"), callContext);
+        validateUserRoles(securityApi.getRoleDefinition("sanity2", callContext), ImmutableList.of("account:charge"));
+
+        securityApi.addRoleDefinition("sanity3", ImmutableList.of("account:charge", "account:credit", "account:*", "invoice:*"), callContext);
+        validateUserRoles(securityApi.getRoleDefinition("sanity3", callContext), ImmutableList.of("account:*", "invoice:*"));
+    }
+
+    @Test(groups = "slow")
+    public void testAuthorization() throws SecurityApiException {
+
+        final String username = "i like";
+        final String password = "c0ff33";
+
+        securityApi.addRoleDefinition("restricted", ImmutableList.of("account:*", "invoice", "tag:create_tag_definition"), callContext);
+        securityApi.addUserRoles(username, password, ImmutableList.of("restricted"), callContext);
+
+        final AuthenticationToken goodToken = new UsernamePasswordToken(username, password);
+        final Subject subject = securityManager.login(null, goodToken);
+
+        subject.checkPermission(Permission.ACCOUNT_CAN_CHARGE.toString());
+        subject.checkPermission(Permission.INVOICE_CAN_CREDIT.toString());
+        subject.checkPermission(Permission.TAG_CAN_CREATE_TAG_DEFINITION.toString());
+
+        try {
+            subject.checkPermission(Permission.TAG_CAN_DELETE_TAG_DEFINITION.toString());
+            Assert.fail("Subject should not have rights to delete tag definitions");
+        } catch (AuthorizationException e) {
+        }
+        subject.logout();
+
+        securityApi.addRoleDefinition("newRestricted", ImmutableList.of("account:*", "invoice", "tag:delete_tag_definition"), callContext);
+        securityApi.updateUserRoles(username, ImmutableList.of("newRestricted"), callContext);
+
+
+        final Subject newSubject = securityManager.login(null, goodToken);
+        newSubject.checkPermission(Permission.ACCOUNT_CAN_CHARGE.toString());
+        newSubject.checkPermission(Permission.INVOICE_CAN_CREDIT.toString());
+        newSubject.checkPermission(Permission.TAG_CAN_DELETE_TAG_DEFINITION.toString());
+
+        try {
+            newSubject.checkPermission(Permission.TAG_CAN_CREATE_TAG_DEFINITION.toString());
+            Assert.fail("Subject should not have rights to create tag definitions");
+        } catch (AuthorizationException e) {
+        }
+
+    }
+
+    private void testInvalidPermissionScenario(final List<String> permissions) {
+        try {
+            securityApi.addRoleDefinition("failed", permissions, callContext);
+            Assert.fail("Should fail permissions " + permissions + " were invalid");
+        } catch (SecurityApiException expected) {
+            Assert.assertEquals(expected.getCode(), ErrorCode.SECURITY_INVALID_PERMISSIONS.getCode());
+        }
+    }
+
+    private void validateUserRoles(final List<String> roles, final List<String> expectedRoles) {
+        Assert.assertEquals(roles.size(), expectedRoles.size());
+        for (final String cur : expectedRoles) {
+            boolean found = false;
+            if (Iterables.tryFind(roles, new Predicate<String>() {
+                @Override
+                public boolean apply(final String input) {
+                    return input.equals(cur);
+                }
+            }).orNull() != null) {
+                found = true;
+            }
+            Assert.assertTrue(found, "Cannot find role " + cur);
+        }
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
index 60461c9..566518d 100644
--- a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
+++ b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
@@ -20,6 +20,8 @@ import javax.inject.Singleton;
 
 import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.authz.UnauthenticatedException;
+import org.killbill.billing.util.glue.TestSecurityModuleNoDB;
+import org.killbill.billing.util.glue.TestUtilModuleNoDB.ShiroModuleNoDB;
 import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
 import org.testng.Assert;
@@ -74,9 +76,9 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
         // Shutdown the cache manager to avoid duplicate exceptions
         CacheManager.getInstance().shutdown();
         final Injector injector = Guice.createInjector(Stage.PRODUCTION,
-                                                       new KillBillShiroModule(configSource),
+                                                       new ShiroModuleNoDB(configSource),
                                                        new KillBillShiroAopModule(),
-                                                       new SecurityModule(configSource),
+                                                       new TestSecurityModuleNoDB(configSource),
                                                        new AbstractModule() {
                                                            @Override
                                                            protected void configure() {
@@ -102,9 +104,9 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
         // Shutdown the cache manager to avoid duplicate exceptions
         CacheManager.getInstance().shutdown();
         final Injector injector = Guice.createInjector(Stage.PRODUCTION,
-                                                       new KillBillShiroModule(configSource),
+                                                       new ShiroModuleNoDB(configSource),
                                                        new KillBillShiroAopModule(),
-                                                       new SecurityModule(configSource),
+                                                       new TestSecurityModuleNoDB(configSource),
                                                        new AbstractModule() {
                                                            @Override
                                                            public void configure() {
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
index bb960a0..9bb60b2 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
@@ -82,6 +82,8 @@ public class UtilTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     @AfterMethod(groups = "fast")
     public void afterMethod() throws Exception {
         eventBus.stop();
+        // Reset the security manager
+        ThreadContext.unbindSecurityManager();
     }
 
     // Security helpers
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
index b29dcd3..69abe55 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -18,6 +18,8 @@ package org.killbill.billing.util;
 
 import javax.inject.Inject;
 
+import org.killbill.billing.security.api.SecurityApi;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
 import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -77,6 +79,9 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
     protected IDBI idbi;
     @Inject
     protected TestApiListener eventsListener;
+    @Inject
+    protected SecurityApi securityApi;
+
 
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {