Repository: gravitee-io/graviteeio-access-management
Pull request #928 (2020-10-05 07:11:53)

feat(idp): add JDBC identity provider
closes gravitee-io/issues#4354


Patch:
diff --git a/Makefile b/Makefile
index 780535cd24..0ad1bcff7c 100644
--- a/Makefile
+++ b/Makefile
@@ -290,6 +290,7 @@ pluginsGateway: # Copy plugins to Gateway
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-mongo/target/gravitee-am-identityprovider-mongo-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-oauth2-generic/target/gravitee-am-identityprovider-oauth2-generic-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-azure-ad/target/gravitee-am-identityprovider-azure-ad-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
+	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/target/gravitee-am-identityprovider-jdbc-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-reporter/gravitee-am-reporter-mongodb/target/gravitee-am-reporter-mongodb-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-repository/gravitee-am-repository-mongodb/target/gravitee-am-repository-mongodb-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-factor/gravitee-am-factor-otp/target/gravitee-am-factor-otp-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
@@ -310,6 +311,7 @@ pluginsManagement: # Copy plugins to Management API
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-oauth2-generic/target/gravitee-am-identityprovider-oauth2-generic-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-oauth2-generic/target/gravitee-am-identityprovider-oauth2-generic-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-azure-ad/target/gravitee-am-identityprovider-azure-ad-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
+	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/target/gravitee-am-identityprovider-jdbc-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-reporter/gravitee-am-reporter-mongodb/target/gravitee-am-reporter-mongodb-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-repository/gravitee-am-repository-mongodb/target/gravitee-am-repository-mongodb-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-factor/gravitee-am-factor-otp/target/gravitee-am-factor-otp-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/idp/impl/IdentityProviderManagerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/idp/impl/IdentityProviderManagerImpl.java
index 8c276fe754..79c0a12e1e 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/idp/impl/IdentityProviderManagerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/idp/impl/IdentityProviderManagerImpl.java
@@ -19,6 +19,7 @@ import io.gravitee.am.common.event.EventManager;
 import io.gravitee.am.common.event.IdentityProviderEvent;
 import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager;
 import io.gravitee.am.gateway.handler.common.certificate.CertificateManager;
+import io.gravitee.am.gateway.handler.context.provider.UserProperties;
 import io.gravitee.am.identityprovider.api.AuthenticationProvider;
 import io.gravitee.am.identityprovider.api.UserProvider;
 import io.gravitee.am.model.Domain;
@@ -166,6 +167,8 @@ public class IdentityProviderManagerImpl extends AbstractService implements Iden
                 providers.put(identityProvider.getId(), authenticationProvider);
                 identities.put(identityProvider.getId(), identityProvider);
                 if (userProvider != null) {
+                    // start the user provider
+                    userProvider.start();
                     userProviders.put(identityProvider.getId(), userProvider);
                 } else {
                     userProviders.remove(identityProvider.getId());
@@ -184,15 +187,23 @@ public class IdentityProviderManagerImpl extends AbstractService implements Iden
 
     private void clearProvider(String identityProviderId) {
         AuthenticationProvider authenticationProvider = providers.remove(identityProviderId);
+        UserProvider userProvider = userProviders.remove(identityProviderId);
+        identities.remove(identityProviderId);
         if (authenticationProvider != null) {
             // stop the authentication provider
             try {
                 authenticationProvider.stop();
             } catch (Exception e) {
-                logger.error("An error occurs while stopping the identity provider : {}", identityProviderId, e);
+                logger.error("An error has occurred while stopping the authentication provider : {}", identityProviderId, e);
+            }
+        }
+        if (userProvider != null) {
+            // stop the user provider
+            try {
+                userProvider.stop();
+            } catch (Exception e) {
+                logger.error("An error has occurred while stopping the user provider : {}", identityProviderId, e);
             }
         }
-        identities.remove(identityProviderId);
-        userProviders.remove(identityProviderId);
     }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
index 2853492734..97222146ce 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
@@ -129,6 +129,14 @@
             <scope>runtime</scope>
         </dependency>
 
+        <dependency>
+            <groupId>io.gravitee.am.identityprovider</groupId>
+            <artifactId>gravitee-am-identityprovider-jdbc</artifactId>
+            <version>${project.version}</version>
+            <type>zip</type>
+            <scope>runtime</scope>
+        </dependency>
+
         <dependency>
             <groupId>io.gravitee.am.certificate</groupId>
             <artifactId>gravitee-am-certificate-javakeystore</artifactId>
@@ -342,6 +350,12 @@
                                     <version>${project.version}</version>
                                     <type>zip</type>
                                 </artifactItem>
+                                <artifactItem>
+                                    <groupId>io.gravitee.am.identityprovider</groupId>
+                                    <artifactId>gravitee-am-identityprovider-jdbc</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                </artifactItem>
 
                                 <!-- Certificates -->
                                 <artifactItem>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-api/src/main/java/io/gravitee/am/identityprovider/api/UserProvider.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-api/src/main/java/io/gravitee/am/identityprovider/api/UserProvider.java
index 75f2217c56..c3cbcf1321 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-api/src/main/java/io/gravitee/am/identityprovider/api/UserProvider.java
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-api/src/main/java/io/gravitee/am/identityprovider/api/UserProvider.java
@@ -15,6 +15,8 @@
  */
 package io.gravitee.am.identityprovider.api;
 
+import io.gravitee.common.component.Lifecycle;
+import io.gravitee.common.service.Service;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
@@ -23,7 +25,7 @@ import io.reactivex.Single;
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
  */
-public interface UserProvider {
+public interface UserProvider extends Service<UserProvider> {
 
     Maybe<User> findByUsername(String username);
 
@@ -32,4 +34,16 @@ public interface UserProvider {
     Single<User> update(String id, User updateUser);
 
     Completable delete(String id);
+
+    default Lifecycle.State lifecycleState() {
+        return Lifecycle.State.INITIALIZED;
+    }
+
+    default UserProvider start() throws Exception {
+        return this;
+    }
+
+    default UserProvider stop() throws Exception {
+        return this;
+    }
 }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/pom.xml
new file mode 100644
index 0000000000..4e0bc613b8
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/pom.xml
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>gravitee-am-identityprovider</artifactId>
+        <groupId>io.gravitee.am.identityprovider</groupId>
+        <version>3.3.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>gravitee-am-identityprovider-jdbc</artifactId>
+
+    <version>3.3.0-SNAPSHOT</version>
+    <name>Gravitee IO - Access Management - Identity Provider - JDBC</name>
+
+    <properties>
+        <json-schema-generator-maven-plugin.version>1.3.0</json-schema-generator-maven-plugin.version>
+        <json-schema-generator-maven-plugin.outputDirectory>${project.build.directory}/schemas</json-schema-generator-maven-plugin.outputDirectory>
+        <r2dbc-pool.version>0.8.4.RELEASE</r2dbc-pool.version>
+        <r2dbc-postgresql.version>0.8.5.RELEASE</r2dbc-postgresql.version>
+        <r2dbc-mssql.version>0.8.4.RELEASE</r2dbc-mssql.version>
+        <r2dbc-mysql.version>0.8.2.RELEASE</r2dbc-mysql.version>
+        <r2dbc-mariadb.version>0.8.4-rc</r2dbc-mariadb.version>
+        <testcontainers.version>1.14.3</testcontainers.version>
+    </properties>
+
+    <dependencies>
+        <!-- Gravitee.io -->
+        <dependency>
+            <groupId>io.gravitee.am.identityprovider</groupId>
+            <artifactId>gravitee-am-identityprovider-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.gravitee.am.service</groupId>
+            <artifactId>gravitee-am-service</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- R2DBC -->
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-pool</artifactId>
+            <version>${r2dbc-pool.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-postgresql</artifactId>
+            <version>${r2dbc-postgresql.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-mssql</artifactId>
+            <version>${r2dbc-mssql.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>dev.miku</groupId>
+            <artifactId>r2dbc-mysql</artifactId>
+            <version>${r2dbc-mysql.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mariadb</groupId>
+            <artifactId>r2dbc-mariadb</artifactId>
+            <version>${r2dbc-mariadb.version}</version>
+        </dependency>
+
+        <!-- Spring -->
+        <dependency>
+            <groupId>org.springframework.</groupId>
+            <artifactId>spring-context</artifactId>
+            <version>${spring.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Jackson -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Vert.x -->
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-rx-java2</artifactId>
+            <version>${vertx.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-web-client</artifactId>
+            <version>${vertx.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <version>${spring.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <version>1.14.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>r2dbc</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>mysql</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.21</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>mariadb</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mariadb.jdbc</groupId>
+            <artifactId>mariadb-java-client</artifactId>
+            <version>2.7.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>mssqlserver</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+            <version>8.4.1.jre8</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.3</version>
+                <configuration>
+                    <appendAssemblyId>false</appendAssemblyId>
+                    <descriptors>
+                        <descriptor>src/main/assembly/plugin-assembly.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-plugin-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/assembly/plugin-assembly.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/assembly/plugin-assembly.xml
new file mode 100644
index 0000000000..4de4108175
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/assembly/plugin-assembly.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed 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.
+
+-->
+<assembly>
+	<id>plugin</id>
+	<formats>
+		<format>zip</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+
+	<!-- Include the main plugin Jar file -->
+	<files>
+		<file>
+			<source>${project.build.directory}/${project.build.finalName}.jar</source>
+		</file>
+	</files>
+
+	<fileSets>
+		<!-- Then include identity provider configuration schemas -->
+		<fileSet>
+			<directory>src/main/resources/schemas</directory>
+			<outputDirectory>schemas</outputDirectory>
+		</fileSet>
+
+		<!-- Create the empty lib directory in case of no libraries is required -->
+		<!-- As there is no maven-assembly-plugin's method do to that, we hack it ourself -->
+		<fileSet>
+			<directory>${project.basedir}/src/assembly</directory>
+			<outputDirectory>lib</outputDirectory>
+			<excludes>
+				<exclude>*</exclude>
+			</excludes>
+		</fileSet>
+	</fileSets>
+
+	<!-- Finally include plugin dependencies -->
+	<dependencySets>
+		<dependencySet>
+			<outputDirectory>lib</outputDirectory>
+			<useProjectArtifact>false</useProjectArtifact>
+		</dependencySet>
+	</dependencySets>
+</assembly>
\ No newline at end of file
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProvider.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProvider.java
new file mode 100644
index 0000000000..00e731b066
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProvider.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc;
+
+import io.gravitee.am.identityprovider.api.*;
+import io.gravitee.am.identityprovider.jdbc.authentication.JdbcAuthenticationProvider;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcIdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.user.JdbcUserProvider;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class JdbcIdentityProvider implements IdentityProvider {
+
+    @Override
+    public Class<? extends IdentityProviderConfiguration> configuration() {
+        return JdbcIdentityProviderConfiguration.class;
+    }
+
+    @Override
+    public Class<? extends AuthenticationProvider> authenticationProvider() {
+        return JdbcAuthenticationProvider.class;
+    }
+
+    @Override
+    public Class<? extends UserProvider> userProvider() {
+        return JdbcUserProvider.class;
+    }
+
+    @Override
+    public Class<? extends IdentityProviderMapper> mapper() {
+        return JdbcIdentityProviderMapper.class;
+    }
+
+    @Override
+    public Class<? extends IdentityProviderRoleMapper> roleMapper() {
+        return JdbcIdentityProviderRoleMapper.class;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProviderMapper.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProviderMapper.java
new file mode 100644
index 0000000000..02f10758cd
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProviderMapper.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc;
+
+import io.gravitee.am.identityprovider.api.IdentityProviderMapper;
+
+import java.util.Map;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class JdbcIdentityProviderMapper implements IdentityProviderMapper {
+    private Map<String, String> mappers;
+
+    public Map<String, String> getMappers() {
+        return this.mappers;
+    }
+
+    public void setMappers(Map<String, String> mappers) {
+        this.mappers = mappers;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProviderRoleMapper.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProviderRoleMapper.java
new file mode 100644
index 0000000000..9d292ca155
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/JdbcIdentityProviderRoleMapper.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc;
+
+import io.gravitee.am.identityprovider.api.IdentityProviderRoleMapper;
+
+import java.util.Map;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class JdbcIdentityProviderRoleMapper implements IdentityProviderRoleMapper {
+    private Map<String, String[]> roles;
+
+    public Map<String, String[]> getRoles() {
+        return this.roles;
+    }
+
+    public void setRoles(Map<String, String[]> roles) {
+        this.roles = roles;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider.java
new file mode 100644
index 0000000000..15eb863320
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider.java
@@ -0,0 +1,265 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.authentication;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.gravitee.am.common.exception.authentication.BadCredentialsException;
+import io.gravitee.am.common.exception.authentication.UsernameNotFoundException;
+import io.gravitee.am.common.oidc.StandardClaims;
+import io.gravitee.am.identityprovider.api.Authentication;
+import io.gravitee.am.identityprovider.api.AuthenticationProvider;
+import io.gravitee.am.identityprovider.api.DefaultUser;
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.identityprovider.jdbc.JdbcIdentityProviderMapper;
+import io.gravitee.am.identityprovider.jdbc.JdbcIdentityProviderRoleMapper;
+import io.gravitee.am.identityprovider.jdbc.authentication.spring.JdbcAuthenticationProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcIdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.utils.ColumnMapRowMapper;
+import io.gravitee.am.identityprovider.jdbc.utils.ObjectUtils;
+import io.gravitee.am.identityprovider.jdbc.utils.ParametersUtils;
+import io.gravitee.am.service.authentication.crypto.password.PasswordEncoder;
+import io.gravitee.common.service.AbstractService;
+import io.r2dbc.pool.ConnectionPool;
+import io.r2dbc.pool.ConnectionPoolConfiguration;
+import io.r2dbc.spi.ConnectionFactories;
+import io.r2dbc.spi.ConnectionFactory;
+import io.r2dbc.spi.ConnectionFactoryOptions;
+import io.r2dbc.spi.Option;
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+
+import java.util.*;
+
+import static io.r2dbc.spi.ConnectionFactoryOptions.*;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Import(JdbcAuthenticationProviderConfiguration.class)
+public class JdbcAuthenticationProvider extends AbstractService<AuthenticationProvider> implements AuthenticationProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(JdbcAuthenticationProvider.class);
+
+    @Autowired
+    private JdbcIdentityProviderConfiguration configuration;
+
+    @Autowired
+    private JdbcIdentityProviderMapper mapper;
+
+    @Autowired
+    private JdbcIdentityProviderRoleMapper roleMapper;
+
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+
+    private ConnectionPool connectionPool;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    protected void doStart() throws Exception {
+        super.doStart();
+
+        LOGGER.info("Initializing connection pool for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+
+        Builder builder = ConnectionFactoryOptions.builder()
+                .option(DRIVER, "pool")
+                .option(PROTOCOL, configuration.getProtocol())
+                .option(HOST, configuration.getHost())
+                .option(PORT, configuration.getPort())
+                .option(USER, configuration.getUser())
+                .option(PASSWORD, configuration.getPassword())
+                .option(DATABASE, configuration.getDatabase());
+
+        List<Map<String, String>> options = configuration.getOptions();
+        if (options != null && !options.isEmpty()) {
+            options.forEach(claimMapper -> {
+                String option = claimMapper.get("option");
+                String value = claimMapper.get("value");
+                builder.option(Option.valueOf(option), ObjectUtils.stringToValue(value));
+            });
+        }
+
+        connectionPool = (ConnectionPool) ConnectionFactories.get(builder.build());
+        LOGGER.info("Connection pool created for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        super.doStop();
+
+        try {
+            LOGGER.info("Disposing connection pool for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+            if (!connectionPool.isDisposed()) {
+                connectionPool.dispose();
+                LOGGER.info("Connection pool disposed for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+            }
+        } catch (Exception ex) {
+            LOGGER.error("An error has occurred while disposing connection pool for database server {} on host {}", configuration.getProtocol(), configuration.getHost(), ex);
+        }
+    }
+
+    @Override
+    public Maybe<User> loadUserByUsername(Authentication authentication) {
+        final String username = authentication.getPrincipal().toString();
+        final String presentedPassword = authentication.getCredentials().toString();
+
+        return selectUserByUsername(username)
+                .switchIfEmpty(Maybe.error(new UsernameNotFoundException(username)))
+                .map(result -> {
+                    // check password
+                    String password = result.get(configuration.getPasswordAttribute()).toString();
+                    if (password == null) {
+                        LOGGER.debug("Authentication failed: password is null");
+                        throw new BadCredentialsException("Invalid account");
+                    }
+
+                    if (!passwordEncoder.matches(presentedPassword, password)) {
+                        LOGGER.debug("Authentication failed: password does not match stored value");
+                        throw new BadCredentialsException("Bad credentials");
+                    }
+                    // create the user
+                    return createUser(result);
+                });
+    }
+
+    @Override
+    public Maybe<User> loadUserByUsername(String username) {
+        return selectUserByUsername(username)
+                .map(this::createUser);
+    }
+
+    public void setConnectionPool(ConnectionPool connectionPool) {
+        this.connectionPool = connectionPool;
+    }
+
+    private Maybe<Map<String, Object>> selectUserByUsername(String username) {
+        final String sql = String.format(configuration.getSelectUserByUsernameQuery(), getIndexParameter("username"));
+        return Flowable.fromPublisher(connectionPool.create())
+                .flatMap(connection -> Flowable.fromPublisher(connection.createStatement(sql).bind(0, username).execute())
+                        .doFinally(() -> Completable.fromPublisher(connection.close()).subscribe()))
+                .flatMap(result -> result.map(ColumnMapRowMapper::mapRow))
+                .firstElement();
+    }
+
+    private User createUser(Map<String, Object> claims) {
+        // get username
+        String username = claims.get(configuration.getUsernameAttribute()).toString();
+        // get sub
+        String sub = claims.containsKey(configuration.getIdentifierAttribute()) ? claims.get(configuration.getIdentifierAttribute()).toString() : username;
+        // compute metadata
+        computeMetadata(claims);
+
+        // create the user
+        DefaultUser user = new DefaultUser(username);
+        // set technical id
+        user.setId(sub);
+        // set user roles
+        user.setRoles(applyRoleMapping(claims));
+        // set additional information
+        Map<String, Object> additionalInformation = new HashMap<>();
+        additionalInformation.put(StandardClaims.SUB, sub);
+        additionalInformation.put(StandardClaims.PREFERRED_USERNAME, username);
+        // apply user mapping
+        Map<String, Object> mappedAttributes = applyUserMapping(claims);
+        additionalInformation.putAll(mappedAttributes);
+        // update sub if user mapping has been changed
+        if (additionalInformation.containsKey(StandardClaims.SUB)) {
+            user.setId(additionalInformation.get(StandardClaims.SUB).toString());
+        }
+        // update username if user mapping has been changed
+        if (additionalInformation.containsKey(StandardClaims.PREFERRED_USERNAME)) {
+            user.setUsername(additionalInformation.get(StandardClaims.PREFERRED_USERNAME).toString());
+        }
+        // remove reserved claims
+        additionalInformation.remove(configuration.getIdentifierAttribute());
+        additionalInformation.remove(configuration.getUsernameAttribute());
+        additionalInformation.remove(configuration.getPasswordAttribute());
+        additionalInformation.remove(configuration.getMetadataAttribute());
+        user.setAdditionalInformation(additionalInformation);
+
+        return user;
+    }
+
+    private Map<String, Object> applyUserMapping(Map<String, Object> attributes) {
+        if (!mappingEnabled()) {
+            return attributes;
+        }
+
+        Map<String, Object> claims = new HashMap<>();
+        this.mapper.getMappers().forEach((k, v) -> {
+            if (attributes.containsKey(v)) {
+                claims.put(k, attributes.get(v));
+            }
+        });
+        return claims;
+    }
+
+    private List<String> applyRoleMapping(Map<String, Object> attributes) {
+        if (!roleMappingEnabled()) {
+            return Collections.emptyList();
+        }
+
+        Set<String> roles = new HashSet<>();
+        roleMapper.getRoles().forEach((role, users) -> {
+            Arrays.asList(users).forEach(u -> {
+                // role mapping have the following syntax userAttribute=userValue
+                String[] roleMapping = u.split("=", 2);
+                String userAttribute = roleMapping[0];
+                String userValue = roleMapping[1];
+                if (attributes.containsKey(userAttribute)) {
+                    Object attribute = attributes.get(userAttribute);
+                    // attribute is a list
+                    if (attribute instanceof Collection && ((Collection) attribute).contains(userValue)) {
+                        roles.add(role);
+                    } else if (userValue.equals(attributes.get(userAttribute))) {
+                        roles.add(role);
+                    }
+                }
+            });
+        });
+        return new ArrayList<>(roles);
+    }
+
+    private boolean mappingEnabled() {
+        return this.mapper != null && this.mapper.getMappers() != null && !this.mapper.getMappers().isEmpty();
+    }
+
+    private boolean roleMappingEnabled() {
+        return this.roleMapper != null && this.roleMapper.getRoles() != null && !this.roleMapper.getRoles().isEmpty();
+    }
+
+    private String getIndexParameter(String field) {
+        return ParametersUtils.getIndexParameter(configuration.getProtocol(), 1, field);
+    }
+
+    private void computeMetadata(Map<String, Object> claims) {
+        Object metadata = claims.get(configuration.getMetadataAttribute());
+        if (metadata == null) {
+            return;
+        }
+        try {
+            claims.putAll(objectMapper.readValue(claims.get(configuration.getMetadataAttribute()).toString(), Map.class));
+        } catch (Exception e) {
+        }
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/authentication/spring/JdbcAuthenticationProviderConfiguration.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/authentication/spring/JdbcAuthenticationProviderConfiguration.java
new file mode 100644
index 0000000000..70bb624b60
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/authentication/spring/JdbcAuthenticationProviderConfiguration.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.authentication.spring;
+
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcIdentityProviderConfiguration;
+import io.gravitee.am.service.authentication.crypto.password.NoOpPasswordEncoder;
+import io.gravitee.am.service.authentication.crypto.password.PasswordEncoder;
+import io.gravitee.am.service.authentication.crypto.password.bcrypt.BCryptPasswordEncoder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class JdbcAuthenticationProviderConfiguration {
+
+    @Autowired
+    private JdbcIdentityProviderConfiguration configuration;
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        if (configuration.getPasswordEncoder() != null &&
+                io.gravitee.am.identityprovider.jdbc.utils.PasswordEncoder.BCRYPT.getValue().equals(configuration.getPasswordEncoder())) {
+            return new BCryptPasswordEncoder();
+        }
+        return NoOpPasswordEncoder.getInstance();
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcIdentityProviderConfiguration.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcIdentityProviderConfiguration.java
new file mode 100644
index 0000000000..586549e750
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcIdentityProviderConfiguration.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.configuration;
+
+import io.gravitee.am.identityprovider.api.IdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.utils.PasswordEncoder;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class JdbcIdentityProviderConfiguration implements IdentityProviderConfiguration {
+
+    public static final String FIELD_ID = "id";
+    public static final String FIELD_USERNAME = "username";
+    public static final String FIELD_PASSWORD = "password";
+    public static final String FIELD_EMAIL = "email";
+    public static final String FIELD_METADATA = "metadata";
+    private String host;
+    private Integer port;
+    private String protocol;
+    private String database;
+    private String usersTable = "users";
+    private String user;
+    private String password;
+    private String selectUserByUsernameQuery;
+    private String identifierAttribute = FIELD_ID;
+    private String usernameAttribute = FIELD_USERNAME;
+    private String passwordAttribute = FIELD_PASSWORD;
+    private String passwordEncoder = PasswordEncoder.BCRYPT.getValue();
+    private List<Map<String, String>> options;
+
+    @Override
+    public boolean userProvider() {
+        return true;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public Integer getPort() {
+        return port;
+    }
+
+    public void setPort(Integer port) {
+        this.port = port;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    public String getDatabase() {
+        return database;
+    }
+
+    public void setDatabase(String database) {
+        this.database = database;
+    }
+
+    public String getUsersTable() {
+        return usersTable;
+    }
+
+    public void setUsersTable(String usersTable) {
+        this.usersTable = usersTable;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getSelectUserByUsernameQuery() {
+        return selectUserByUsernameQuery;
+    }
+
+    public void setSelectUserByUsernameQuery(String selectUserByUsernameQuery) {
+        this.selectUserByUsernameQuery = selectUserByUsernameQuery;
+    }
+
+    public String getIdentifierAttribute() {
+        return identifierAttribute;
+    }
+
+    public void setIdentifierAttribute(String identifierAttribute) {
+        this.identifierAttribute = identifierAttribute;
+    }
+
+    public String getUsernameAttribute() {
+        return usernameAttribute;
+    }
+
+    public void setUsernameAttribute(String usernameAttribute) {
+        this.usernameAttribute = usernameAttribute;
+    }
+
+    public String getPasswordAttribute() {
+        return passwordAttribute;
+    }
+
+    public void setPasswordAttribute(String passwordAttribute) {
+        this.passwordAttribute = passwordAttribute;
+    }
+
+    public String getPasswordEncoder() {
+        return passwordEncoder;
+    }
+
+    public void setPasswordEncoder(String passwordEncoder) {
+        this.passwordEncoder = passwordEncoder;
+    }
+
+    public List<Map<String, String>> getOptions() {
+        return options;
+    }
+
+    public void setOptions(List<Map<String, String>> options) {
+        this.options = options;
+    }
+
+    public String getEmailAttribute() {
+        return FIELD_EMAIL;
+    }
+
+    public String getMetadataAttribute() {
+        return FIELD_METADATA;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider.java
new file mode 100644
index 0000000000..61f5329987
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider.java
@@ -0,0 +1,299 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.user;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.gravitee.am.common.oidc.StandardClaims;
+import io.gravitee.am.common.utils.RandomString;
+import io.gravitee.am.identityprovider.api.DefaultUser;
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.identityprovider.api.UserProvider;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcIdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.user.spring.JdbcUserProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.utils.ColumnMapRowMapper;
+import io.gravitee.am.identityprovider.jdbc.utils.ObjectUtils;
+import io.gravitee.am.identityprovider.jdbc.utils.ParametersUtils;
+import io.gravitee.am.service.authentication.crypto.password.PasswordEncoder;
+import io.gravitee.am.service.exception.UserAlreadyExistsException;
+import io.gravitee.am.service.exception.UserNotFoundException;
+import io.gravitee.common.service.AbstractService;
+import io.r2dbc.pool.ConnectionPool;
+import io.r2dbc.pool.ConnectionPoolConfiguration;
+import io.r2dbc.spi.*;
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static io.r2dbc.spi.ConnectionFactoryOptions.*;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Import(JdbcUserProviderConfiguration.class)
+public class JdbcUserProvider extends AbstractService<UserProvider> implements UserProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(JdbcUserProvider.class);
+
+    @Autowired
+    private JdbcIdentityProviderConfiguration configuration;
+
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+
+    private ConnectionPool connectionPool;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    protected void doStart() throws Exception {
+        super.doStart();
+
+        LOGGER.info("Initializing connection pool for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+        Builder builder = ConnectionFactoryOptions.builder()
+                .option(DRIVER, "pool")
+                .option(PROTOCOL, configuration.getProtocol())
+                .option(HOST, configuration.getHost())
+                .option(PORT, configuration.getPort())
+                .option(USER, configuration.getUser())
+                .option(PASSWORD, configuration.getPassword())
+                .option(DATABASE, configuration.getDatabase());
+
+        List<Map<String, String>> options = configuration.getOptions();
+        if (options != null && !options.isEmpty()) {
+            options.forEach(claimMapper -> {
+                String option = claimMapper.get("option");
+                String value = claimMapper.get("value");
+                builder.option(Option.valueOf(option), ObjectUtils.stringToValue(value));
+            });
+        }
+
+        connectionPool = (ConnectionPool) ConnectionFactories.get(builder.build());
+        LOGGER.info("Connection pool created for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        super.doStop();
+
+        try {
+            LOGGER.info("Disposing connection pool for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+            if (!connectionPool.isDisposed()) {
+                connectionPool.dispose();
+                LOGGER.info("Connection pool disposed for database server {} on host {}", configuration.getProtocol(), configuration.getHost());
+            }
+        } catch (Exception ex) {
+            LOGGER.error("An error has occurred while disposing connection pool for database server {} on host {}", configuration.getProtocol(), configuration.getHost(), ex);
+        }
+    }
+
+    @Override
+    public Maybe<User> findByUsername(String username) {
+        return selectUserByUsername(username)
+                .map(result -> createUser(result));
+    }
+
+    @Override
+    public Single<User> create(User user) {
+        // set technical id
+        ((DefaultUser)user).setId(user.getId() != null ? user.getId() : RandomString.generate());
+
+        return selectUserByUsername(user.getUsername())
+                .isEmpty()
+                .flatMap(isEmpty -> {
+                    if (!isEmpty) {
+                        return Single.error(new UserAlreadyExistsException(user.getUsername()));
+                    } else {
+                        final String sql = String.format("INSERT INTO %s (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)",
+                                configuration.getUsersTable(),
+                                configuration.getIdentifierAttribute(),
+                                configuration.getUsernameAttribute(),
+                                configuration.getPasswordAttribute(),
+                                configuration.getEmailAttribute(),
+                                configuration.getMetadataAttribute(),
+                                getIndexParameter(1, "id"),
+                                getIndexParameter(2, "username"),
+                                getIndexParameter(3, "password"),
+                                getIndexParameter(4, "email"),
+                                getIndexParameter(5, "metadata"));
+
+                        Object[] args = new Object[5];
+                        args[0] = user.getId();
+                        args[1] = user.getUsername();
+                        args[2] = user.getCredentials() != null ? passwordEncoder.encode(user.getCredentials()) : null;
+                        args[3] = user.getEmail();
+                        args[4] = user.getAdditionalInformation() != null ? objectMapper.writeValueAsString(user.getAdditionalInformation()) : null;
+
+                        return query(sql, args)
+                                .flatMap(Result::getRowsUpdated)
+                                .first(0)
+                                .map(result -> user);
+                    }
+                });
+    }
+
+    @Override
+    public Single<User> update(String id, User updateUser) {
+        final String sql;
+        final Object[] args;
+        final String metadata = convert(updateUser.getAdditionalInformation());
+
+        if (updateUser.getCredentials() != null) {
+            args = new Object[3];
+            sql = String.format("UPDATE %s SET %s = %s, %s = %s WHERE id = %s",
+                    configuration.getUsersTable(),
+                    configuration.getPasswordAttribute(),
+                    getIndexParameter(1, "password"),
+                    configuration.getMetadataAttribute(),
+                    getIndexParameter(2, "metadata"),
+                    getIndexParameter(3, "id"));
+            args[0] = passwordEncoder.encode(updateUser.getCredentials());
+            args[1] = metadata;
+            args[2] = id;
+        } else {
+            args = new Object[2];
+            sql = String.format("UPDATE %s SET %s = %s WHERE id = %s",
+                    configuration.getUsersTable(),
+                    configuration.getMetadataAttribute(),
+                    getIndexParameter(1, "metadata"),
+                    getIndexParameter(2, "id"));
+            args[0] = metadata;
+            args[1] = id;
+        }
+
+        return query(sql, args)
+                .flatMap(Result::getRowsUpdated)
+                .first(0)
+                .flatMap(rowsUpdated -> {
+                    if (rowsUpdated == 0) {
+                        return Single.error(new UserNotFoundException(id));
+                    }
+                    return Single.just(updateUser);
+                });
+    }
+
+    @Override
+    public Completable delete(String id) {
+        final String sql = String.format("DELETE FROM %s where id = %s",
+                configuration.getUsersTable(),
+                getIndexParameter(1, "id"));
+
+        return query(sql, id)
+                .flatMap(Result::getRowsUpdated)
+                .flatMapCompletable(rowsUpdated -> {
+                    if (rowsUpdated == 0) {
+                        return Completable.error(new UserNotFoundException(id));
+                    }
+                    return Completable.complete();
+                });
+    }
+
+    public void setConnectionPool(ConnectionPool connectionPool) {
+        this.connectionPool = connectionPool;
+    }
+
+    private Maybe<Map<String, Object>> selectUserByUsername(String username) {
+        final String sql = String.format(configuration.getSelectUserByUsernameQuery(), getIndexParameter(1, "username"));
+        return query(sql, username)
+                .flatMap(result -> result.map(ColumnMapRowMapper::mapRow))
+                .firstElement();
+    }
+
+    private Flowable<Result> query(String sql, Object... args) {
+        return Single.fromPublisher(connectionPool.create())
+                .toFlowable()
+                .flatMap(connection -> {
+                    Statement statement = connection.createStatement(sql);
+                    for (int i = 0; i < args.length; i++) {
+                        Object arg = args[i];
+                        bind(statement, i, arg, arg != null ? arg.getClass() : String.class);
+                    }
+                    return Flowable.fromPublisher(statement.execute())
+                            .doFinally(() -> Completable.fromPublisher(connection.close()).subscribe());
+                });
+    }
+
+    private User createUser(Map<String, Object> claims) {
+        // get username
+        String username = claims.get(configuration.getUsernameAttribute()).toString();
+        // get sub
+        String id = claims.get(configuration.getIdentifierAttribute()).toString();
+        // get encrypted password
+        String password = claims.get(configuration.getPasswordAttribute()) != null ? claims.get(configuration.getPasswordAttribute()).toString() : null;
+        // compute metadata
+        computeMetadata(claims);
+
+        // create the user
+        DefaultUser user = new DefaultUser(username);
+        user.setId(id);
+        user.setCredentials(password);
+        // additional claims
+        Map<String, Object> additionalInformation = new HashMap<>(claims);
+        claims.put(StandardClaims.SUB, id);
+        claims.put(StandardClaims.PREFERRED_USERNAME, username);
+        // remove reserved claims
+        additionalInformation.remove(configuration.getIdentifierAttribute());
+        additionalInformation.remove(configuration.getUsernameAttribute());
+        additionalInformation.remove(configuration.getPasswordAttribute());
+        additionalInformation.remove(configuration.getMetadataAttribute());
+        user.setAdditionalInformation(additionalInformation);
+
+        return user;
+    }
+
+    private void bind(Statement statement, int index, Object value, Class type) {
+        if (value != null) {
+            statement.bind(index, value);
+        } else {
+            statement.bindNull(index, type);
+        }
+    }
+
+    private String getIndexParameter(int index, String field) {
+        return ParametersUtils.getIndexParameter(configuration.getProtocol(), index, field);
+    }
+
+    private void computeMetadata(Map<String, Object> claims) {
+        Object metadata = claims.get(configuration.getMetadataAttribute());
+        if (metadata == null) {
+            return;
+        }
+        try {
+            claims.putAll(objectMapper.readValue(claims.get(configuration.getMetadataAttribute()).toString(), Map.class));
+        } catch (Exception e) {
+        }
+    }
+
+    private String convert(Map<String, Object> claims) {
+        if (claims == null) {
+            return null;
+        }
+        try {
+            return objectMapper.writeValueAsString(claims);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/user/spring/JdbcUserProviderConfiguration.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/user/spring/JdbcUserProviderConfiguration.java
new file mode 100644
index 0000000000..f561256133
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/user/spring/JdbcUserProviderConfiguration.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.user.spring;
+
+import io.gravitee.am.identityprovider.jdbc.authentication.spring.JdbcAuthenticationProviderConfiguration;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+@Import(JdbcAuthenticationProviderConfiguration.class)
+public class JdbcUserProviderConfiguration {
+
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ColumnMapRowMapper.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ColumnMapRowMapper.java
new file mode 100644
index 0000000000..e958181bbc
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ColumnMapRowMapper.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.utils;
+
+import io.r2dbc.spi.Row;
+import io.r2dbc.spi.RowMetadata;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public final class ColumnMapRowMapper {
+
+    public static Map<String, Object> mapRow(Row row, RowMetadata rowMetadata) {
+        Map<String, Object> claims = new HashMap(rowMetadata.getColumnNames()
+                .stream()
+                .filter(c -> row.get(c) != null)
+                .collect(Collectors.toMap(c -> c, c -> row.get(c))));
+        return claims;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ObjectUtils.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ObjectUtils.java
new file mode 100644
index 0000000000..291da99bac
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ObjectUtils.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.utils;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public final class ObjectUtils {
+
+    public static Object stringToValue(String string) {
+        if (string.equals("")) {
+            return string;
+        } else if (string.equalsIgnoreCase("true")) {
+            return Boolean.TRUE;
+        } else if (string.equalsIgnoreCase("false")) {
+            return Boolean.FALSE;
+        } else if (string.equalsIgnoreCase("null")) {
+            return null;
+        } else {
+            char initial = string.charAt(0);
+            if (initial >= '0' && initial <= '9' || initial == '-') {
+                try {
+                    if (isDecimalNotation(string)) {
+                        Double d = Double.valueOf(string);
+                        if (!d.isInfinite() && !d.isNaN()) {
+                            return d;
+                        }
+                    } else {
+                        Long myLong = Long.valueOf(string);
+                        if (string.equals(myLong.toString())) {
+                            if (myLong == (long)myLong.intValue()) {
+                                return myLong.intValue();
+                            }
+
+                            return myLong;
+                        }
+                    }
+                } catch (Exception var3) {
+                }
+            }
+
+            return string;
+        }
+    }
+
+    private static boolean isDecimalNotation(String val) {
+        return val.indexOf(46) > -1 || val.indexOf(101) > -1 || val.indexOf(69) > -1 || "-0".equals(val);
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ParametersUtils.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ParametersUtils.java
new file mode 100644
index 0000000000..c75a0f0fbd
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/ParametersUtils.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.utils;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public final class ParametersUtils {
+
+    public static String getIndexParameter(String database, int index, String field) {
+        switch (database) {
+            case "mysql":
+            case "mariadb":
+                return "?";
+            case "postgresql":
+                return "$" + index;
+            case "sqlserver":
+                return "@" + field;
+            default:
+                return "" + index;
+        }
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/PasswordEncoder.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/PasswordEncoder.java
new file mode 100644
index 0000000000..ac9b349d0b
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/java/io/gravitee/am/identityprovider/jdbc/utils/PasswordEncoder.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.utils;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public enum PasswordEncoder {
+
+    NONE("None"),
+    BCRYPT("BCrypt");
+
+    private final String value;
+
+    PasswordEncoder(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
+
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/resources/plugin.properties b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/resources/plugin.properties
new file mode 100644
index 0000000000..362608347c
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/resources/plugin.properties
@@ -0,0 +1,6 @@
+id=jdbc-am-idp
+name=${project.name}
+version=${project.version}
+description=${project.description}
+class=io.gravitee.am.identityprovider.jdbc.JdbcIdentityProvider
+type=identity_provider
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/resources/schemas/schema-form.json b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/resources/schemas/schema-form.json
new file mode 100644
index 0000000000..8d8e57fb27
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/main/resources/schemas/schema-form.json
@@ -0,0 +1,107 @@
+{
+  "type" : "object",
+  "id" : "urn:jsonschema:io:gravitee:am:identityprovider:jdbc:JdbcIdentityProviderConfiguration",
+  "properties" : {
+    "host" : {
+      "type" : "string",
+      "title": "Database connection host",
+      "description": "IP address or DNS of the database server."
+    },
+    "port" : {
+      "type": "number",
+      "title": "Database connection port",
+      "description": "Database server port number."
+    },
+    "protocol" : {
+      "type": "string",
+      "enum": ["postgresql", "mysql", "sqlserver", "mariadb"],
+      "title": "Database driver identifier",
+      "description": "Type of your relational database (RDBMS). PostgreSQL, MySQL, SQL Server, MariaDB, ..."
+    },
+    "database" : {
+      "type" : "string",
+      "title": "The database used to run query",
+      "description": "The database used to run query to search for users."
+    },
+    "usersTable" : {
+      "type" : "string",
+      "default": "users",
+      "title": "The table used to run query",
+      "description": "The table used to run query to search for users."
+    },
+    "user" : {
+      "type" : "string",
+      "title": "Authentication user",
+      "description": "User to access database."
+    },
+    "password" : {
+      "type" : "string",
+      "title": "Authentication password",
+      "description": "User password.",
+      "widget": "password"
+    },
+    "selectUserByUsernameQuery" : {
+      "type" : "string",
+      "default": "SELECT * FROM users WHERE username = %s",
+      "widget": "textarea",
+      "title": "Query to find for a user using its identifier (username)",
+      "description": "The query which is executed to search for an user using its identifier (username)."
+    },
+    "identifierAttribute": {
+      "type": "string",
+      "title": "User identifier attribute",
+      "default": "id",
+      "description": "Identifier field of your users"
+    },
+    "usernameAttribute" : {
+      "type" : "string",
+      "default": "username",
+      "title": "User username attribute",
+      "description": "Username field of your users"
+    },
+    "passwordAttribute" : {
+      "type" : "string",
+      "default": "password",
+      "title": "User password attribute",
+      "description": "Password field of your users"
+    },
+    "passwordEncoder" : {
+      "type": "string",
+      "enum": ["BCrypt", "None"],
+      "default": "BCrypt",
+      "title": "Password encoder",
+      "description": "The encoding mechanism used to store user password value."
+    },
+    "options" : {
+      "title": "Connection options",
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "option": {
+            "title": "Option",
+            "description": "Connection option name (https://r2dbc.io/ for more information)",
+            "type": "string"
+          },
+          "value": {
+            "title": "Value",
+            "description": "Connection option value (https://r2dbc.io/ for more information)",
+            "type": "string"
+          }
+        }
+      }
+    }
+  },
+  "required": [
+    "host",
+    "protocol",
+    "database",
+    "usersTable",
+    "user",
+    "selectUserByUsernameQuery",
+    "identifierAttribute",
+    "usernameAttribute",
+    "passwordAttribute",
+    "passwordEncoder"
+  ]
+}
diff --git a/gravitee-am-identityprovider/pom.xml b/gravitee-am-identityprovider/pom.xml
index 33a49c34c5..f0efcf6791 100644
--- a/gravitee-am-identityprovider/pom.xml
+++ b/gravitee-am-identityprovider/pom.xml
@@ -45,6 +45,7 @@
         <module>gravitee-am-identityprovider-facebook</module>
         <module>gravitee-am-identityprovider-twitter</module>
         <module>gravitee-am-identityprovider-franceconnect</module>
+        <module>gravitee-am-identityprovider-jdbc</module>
     </modules>
 
     <dependencies>
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java
index c1c91f217c..fb23431de3 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java
@@ -145,7 +145,15 @@ public class IdentityProviderManagerImpl extends AbstractService<IdentityProvide
 
     private void removeUserProvider(String identityProviderId) {
         logger.info("Management API has received a undeploy identity provider event for {}", identityProviderId);
-        userProviders.remove(identityProviderId);
+        UserProvider userProvider = userProviders.remove(identityProviderId);
+        if (userProvider != null) {
+            // stop the user provider
+            try {
+                userProvider.stop();
+            } catch (Exception e) {
+                logger.error("An error has occurred while stopping the user provider : {}", identityProviderId, e);
+            }
+        }
     }
 
     private void loadUserProvider(IdentityProvider identityProvider) {
@@ -153,6 +161,7 @@ public class IdentityProviderManagerImpl extends AbstractService<IdentityProvide
             UserProvider userProvider = identityProviderPluginManager.create(identityProvider.getType(), identityProvider.getConfiguration());
             if (userProvider != null) {
                 logger.info("Initializing user provider : {}", identityProvider.getId());
+                userProvider.start();
                 userProviders.put(identityProvider.getId(), userProvider);
             } else {
                 userProviders.remove(identityProvider.getId());
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
index f57e8d6c28..5d9d48c4ea 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
@@ -124,6 +124,14 @@
             <scope>runtime</scope>
         </dependency>
 
+        <dependency>
+            <groupId>io.gravitee.am.identityprovider</groupId>
+            <artifactId>gravitee-am-identityprovider-jdbc</artifactId>
+            <version>${project.version}</version>
+            <type>zip</type>
+            <scope>runtime</scope>
+        </dependency>
+
         <dependency>
             <groupId>io.gravitee.am.certificate</groupId>
             <artifactId>gravitee-am-certificate-javakeystore</artifactId>
@@ -305,6 +313,12 @@
                                     <version>${project.version}</version>
                                     <type>zip</type>
                                 </artifactItem>
+                                <artifactItem>
+                                    <groupId>io.gravitee.am.identityprovider</groupId>
+                                    <artifactId>gravitee-am-identityprovider-jdbc</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                </artifactItem>
 
                                 <!-- Certificates -->
                                 <artifactItem>
diff --git a/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts b/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts
index 44979561bf..475d3211f2 100644
--- a/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts
+++ b/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts
@@ -36,7 +36,8 @@ export class ApplicationIdPComponent implements OnInit {
     'azure-ad-am-idp': 'Azure AD',
     'twitter-am-idp': 'Twitter',
     'facebook-am-idp': 'Facebook',
-    'franceconnect-am-idp': 'FranceConnect'
+    'franceconnect-am-idp': 'FranceConnect',
+    'jdbc-am-idp': 'JDBC'
   };
   private identityProviderIcons: any = {
     'ldap-am-idp' : 'device_hub',
@@ -47,7 +48,8 @@ export class ApplicationIdPComponent implements OnInit {
     'azure-ad-am-idp': 'cloud_queue',
     'twitter-am-idp': 'cloud_queue',
     'facebook-am-idp': 'cloud_queue',
-    'franceconnect-am-idp': 'cloud_queue'
+    'franceconnect-am-idp': 'cloud_queue',
+    'jdbc-am-idp': 'storage'
   };
   loadIdentities = true;
   application: any;
diff --git a/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts b/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts
index 91c283ddca..0db94cc0ae 100644
--- a/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts
@@ -33,7 +33,8 @@ export class ProviderCreationStep1Component implements OnInit {
     'facebook-am-idp': 'Facebook',
     'http-am-idp': 'HTTP',
     'saml2-generic-am-idp': 'SAML 2.0',
-    'franceconnect-am-idp': 'FranceConnect'
+    'franceconnect-am-idp': 'FranceConnect',
+    'jdbc-am-idp': 'JDBC'
   };
   @Input() provider;
   providers: any[];
diff --git a/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts b/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts
index fbf6f8b477..ef802f9cc7 100644
--- a/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts
@@ -38,7 +38,8 @@ export class DomainSettingsProvidersComponent implements OnInit {
     'azure-ad-am-idp': 'Azure AD',
     'twitter-am-idp': 'Twitter',
     'facebook-am-idp': 'Facebook',
-    'franceconnect-am-idp': 'FranceConnect'
+    'franceconnect-am-idp': 'FranceConnect',
+    'jdbc-am-idp': 'JDBC'
   };
   private identityProviderIcons: any = {
     'ldap-am-idp' : 'device_hub',


Test patch:
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProviderTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProviderTest.java
new file mode 100644
index 0000000000..a774a46017
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProviderTest.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.authentication;
+
+import io.gravitee.am.common.exception.authentication.BadCredentialsException;
+import io.gravitee.am.common.exception.authentication.UsernameNotFoundException;
+import io.gravitee.am.identityprovider.api.Authentication;
+import io.gravitee.am.identityprovider.api.AuthenticationContext;
+import io.gravitee.am.identityprovider.api.AuthenticationProvider;
+import io.gravitee.am.identityprovider.api.User;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+public abstract class JdbcAuthenticationProviderTest {
+
+    @Autowired
+    private AuthenticationProvider authenticationProvider;
+
+    @Test
+    public void shouldLoadUserByUsername_authentication() {
+        TestObserver<User> testObserver = authenticationProvider.loadUserByUsername(new Authentication() {
+            @Override
+            public Object getCredentials() {
+                return "bobspassword";
+            }
+
+            @Override
+            public Object getPrincipal() {
+                return "bob";
+            }
+
+            @Override
+            public AuthenticationContext getContext() {
+                return null;
+            }
+        }).test();
+
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(u -> "bob".equals(u.getUsername()));
+    }
+
+    @Test
+    public void shouldLoadUserByUsername_authentication_badCredentials() {
+        TestObserver<User> testObserver = authenticationProvider.loadUserByUsername(new Authentication() {
+            @Override
+            public Object getCredentials() {
+                return "wrongpassword";
+            }
+
+            @Override
+            public Object getPrincipal() {
+                return "bob";
+            }
+
+            @Override
+            public AuthenticationContext getContext() {
+                return null;
+            }
+        }).test();
+        testObserver.awaitTerminalEvent();
+        testObserver.assertError(BadCredentialsException.class);
+    }
+
+    @Test
+    public void shouldNotLoadUserByUsername_authentication_usernameNotFound() {
+        TestObserver<User> testObserver = authenticationProvider.loadUserByUsername(new Authentication() {
+            @Override
+            public Object getCredentials() {
+                return "bobspassword";
+            }
+
+            @Override
+            public Object getPrincipal() {
+                return "unknownUsername";
+            }
+
+            @Override
+            public AuthenticationContext getContext() {
+                return null;
+            }
+        }).test();
+
+        testObserver.awaitTerminalEvent();
+        testObserver.assertError(UsernameNotFoundException.class);
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MSSQLTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MSSQLTest.java
new file mode 100644
index 0000000000..699ec59180
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MSSQLTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.authentication;
+
+import io.gravitee.am.identityprovider.jdbc.authentication.spring.JdbcAuthenticationProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_MSSQL;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_MSSQL.class, JdbcAuthenticationProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcAuthenticationProvider_MSSQLTest extends JdbcAuthenticationProviderTest { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MariaDBTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MariaDBTest.java
new file mode 100644
index 0000000000..4d663bf377
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MariaDBTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.authentication;
+
+import io.gravitee.am.identityprovider.jdbc.authentication.spring.JdbcAuthenticationProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_MariaDB;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_MariaDB.class, JdbcAuthenticationProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcAuthenticationProvider_MariaDBTest extends JdbcAuthenticationProviderTest { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MySQLTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MySQLTest.java
new file mode 100644
index 0000000000..ab7d162ac4
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_MySQLTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.authentication;
+
+import io.gravitee.am.identityprovider.jdbc.authentication.spring.JdbcAuthenticationProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_MySQL;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_MySQL.class, JdbcAuthenticationProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcAuthenticationProvider_MySQLTest extends JdbcAuthenticationProviderTest { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_PostgreSQLTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_PostgreSQLTest.java
new file mode 100644
index 0000000000..8e4c4e5b91
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/authentication/JdbcAuthenticationProvider_PostgreSQLTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.authentication;
+
+import io.gravitee.am.identityprovider.jdbc.authentication.spring.JdbcAuthenticationProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_PostgreSQL;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_PostgreSQL.class, JdbcAuthenticationProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcAuthenticationProvider_PostgreSQLTest extends JdbcAuthenticationProviderTest { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest.java
new file mode 100644
index 0000000000..a6e8931251
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.configuration;
+
+import io.gravitee.am.identityprovider.api.AuthenticationProvider;
+import io.gravitee.am.identityprovider.api.UserProvider;
+import io.gravitee.am.identityprovider.jdbc.JdbcIdentityProviderMapper;
+import io.gravitee.am.identityprovider.jdbc.JdbcIdentityProviderRoleMapper;
+import io.gravitee.am.identityprovider.jdbc.authentication.JdbcAuthenticationProvider;
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcIdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.jdbc.user.JdbcUserProvider;
+import io.gravitee.am.identityprovider.jdbc.utils.PasswordEncoder;
+import io.r2dbc.pool.ConnectionPool;
+import io.r2dbc.pool.ConnectionPoolConfiguration;
+import io.r2dbc.spi.Connection;
+import io.r2dbc.spi.ConnectionFactories;
+import io.r2dbc.spi.ConnectionFactory;
+import io.reactivex.Completable;
+import io.reactivex.Single;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public abstract class JdbcAuthenticationProviderConfigurationTest implements InitializingBean {
+
+    @Autowired
+    private ConnectionPool connectionPool;
+
+    public abstract String url();
+
+    public abstract String protocol();
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        // create table users and insert values
+        Connection connection = connectionPool.create().block();
+        Single.fromPublisher(connection.createStatement("create table users(id varchar(256), username varchar(256), password varchar(256), email varchar(256), metadata text)").execute()).subscribe();
+        Single.fromPublisher(connection.createStatement("insert into users values('1', 'bob', 'bobspassword', null, null)").execute()).subscribe();
+        Completable.fromPublisher(connection.close()).subscribe();
+    }
+
+    @Bean
+    public ConnectionPool connectionPool() {
+        ConnectionFactory connectionFactory = ConnectionFactories.get(url());
+
+        ConnectionPoolConfiguration connectionPoolConfiguration = ConnectionPoolConfiguration
+                .builder(connectionFactory)
+                .build();
+
+        return new ConnectionPool(connectionPoolConfiguration);
+    }
+
+    @Bean
+    public AuthenticationProvider authenticationProvider() {
+        JdbcAuthenticationProvider jdbcAuthenticationProvider = new JdbcAuthenticationProvider();
+        jdbcAuthenticationProvider.setConnectionPool(connectionPool);
+        return jdbcAuthenticationProvider;
+    }
+
+    @Bean
+    public UserProvider userProvider() {
+        JdbcUserProvider jdbcUserProvider = new JdbcUserProvider();
+        jdbcUserProvider.setConnectionPool(connectionPool);
+        return jdbcUserProvider;
+    }
+
+    @Bean
+    public JdbcIdentityProviderConfiguration configuration() {
+        JdbcIdentityProviderConfiguration configuration = new JdbcIdentityProviderConfiguration();
+        configuration.setProtocol(protocol());
+        configuration.setUsersTable("users");
+        configuration.setSelectUserByUsernameQuery("select * from users where username = %s");
+        configuration.setPasswordEncoder(PasswordEncoder.NONE.getValue());
+
+        return configuration;
+    }
+
+    @Bean
+    public JdbcIdentityProviderMapper mapper() {
+        return new JdbcIdentityProviderMapper();
+    }
+
+    @Bean
+    public JdbcIdentityProviderRoleMapper roleMapper() {
+        return new JdbcIdentityProviderRoleMapper();
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MSSQL.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MSSQL.java
new file mode 100644
index 0000000000..d9632536f7
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MSSQL.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.configuration;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class JdbcAuthenticationProviderConfigurationTest_MSSQL extends JdbcAuthenticationProviderConfigurationTest {
+
+    public String url() {
+        return "r2dbc:tc:sqlserver:///?TC_IMAGE_TAG=2017-CU12";
+    }
+
+    @Override
+    public String protocol() {
+        return "sqlserver";
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MariaDB.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MariaDB.java
new file mode 100644
index 0000000000..e72ae26cc1
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MariaDB.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.configuration;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class JdbcAuthenticationProviderConfigurationTest_MariaDB extends JdbcAuthenticationProviderConfigurationTest {
+
+    public String url() {
+        return "r2dbc:tc:mariadb:///databasename?TC_IMAGE_TAG=10.3.6";
+    }
+
+    @Override
+    public String protocol() {
+        return "mariadb";
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MySQL.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MySQL.java
new file mode 100644
index 0000000000..f2b2ef7a5d
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_MySQL.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.configuration;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class JdbcAuthenticationProviderConfigurationTest_MySQL extends JdbcAuthenticationProviderConfigurationTest {
+
+    public String url() {
+        return "r2dbc:tc:mysql:///databasename?TC_IMAGE_TAG=5.6.23";
+    }
+
+    @Override
+    public String protocol() {
+        return "mysql";
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_PostgreSQL.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_PostgreSQL.java
new file mode 100644
index 0000000000..54c3660542
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/configuration/JdbcAuthenticationProviderConfigurationTest_PostgreSQL.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.configuration;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class JdbcAuthenticationProviderConfigurationTest_PostgreSQL extends JdbcAuthenticationProviderConfigurationTest {
+
+    public String url() {
+        return "r2dbc:tc:postgresql:///databasename?TC_IMAGE_TAG=9.6.8";
+    }
+
+    @Override
+    public String protocol() {
+        return "postgresql";
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MSSQLTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MSSQLTest.java
new file mode 100644
index 0000000000..256cf2d2df
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MSSQLTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.user;
+
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_MSSQL;
+import io.gravitee.am.identityprovider.jdbc.user.spring.JdbcUserProviderConfiguration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_MSSQL.class, JdbcUserProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcUserProvider_MSSQLTest extends JdbcUserProvider_Test { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MariaDBTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MariaDBTest.java
new file mode 100644
index 0000000000..b5ccd2ff0f
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MariaDBTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.user;
+
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_MariaDB;
+import io.gravitee.am.identityprovider.jdbc.user.spring.JdbcUserProviderConfiguration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_MariaDB.class, JdbcUserProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcUserProvider_MariaDBTest extends JdbcUserProvider_Test { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MySQLTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MySQLTest.java
new file mode 100644
index 0000000000..5214ed8998
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_MySQLTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.user;
+
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_MySQL;
+import io.gravitee.am.identityprovider.jdbc.user.spring.JdbcUserProviderConfiguration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_MySQL.class, JdbcUserProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcUserProvider_MySQLTest extends JdbcUserProvider_Test { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_PostgreSQLTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_PostgreSQLTest.java
new file mode 100644
index 0000000000..723c7484be
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_PostgreSQLTest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.user;
+
+import io.gravitee.am.identityprovider.jdbc.configuration.JdbcAuthenticationProviderConfigurationTest_PostgreSQL;
+import io.gravitee.am.identityprovider.jdbc.user.spring.JdbcUserProviderConfiguration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@ContextConfiguration(classes = { JdbcAuthenticationProviderConfigurationTest_PostgreSQL.class, JdbcUserProviderConfiguration.class }, loader = AnnotationConfigContextLoader.class)
+public class JdbcUserProvider_PostgreSQLTest extends JdbcUserProvider_Test { }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_Test.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_Test.java
new file mode 100644
index 0000000000..24ca72c15c
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/java/io/gravitee/am/identityprovider/jdbc/user/JdbcUserProvider_Test.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.identityprovider.jdbc.user;
+
+import io.gravitee.am.identityprovider.api.DefaultUser;
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.identityprovider.api.UserProvider;
+import io.gravitee.am.service.exception.UserNotFoundException;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+public abstract class JdbcUserProvider_Test {
+
+    @Autowired
+    private UserProvider userProvider;
+
+    @Test
+    public void shouldSelectUserByUsername() {
+        TestObserver<User> testObserver = userProvider.findByUsername("bob").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(u -> "bob".equals(u.getUsername()));
+    }
+
+    @Test
+    public void shouldNotSelectUserByUsername_userNotFound() {
+        TestObserver<User> testObserver = userProvider.findByUsername("unknown").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoValues();
+    }
+
+    @Test
+    public void shouldCreate() {
+        DefaultUser user = new DefaultUser("username1");
+        TestObserver<User> testObserver = userProvider.create(user).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertValue(u -> u.getId() != null);
+    }
+
+    @Test
+    public void shouldUpdate() {
+        DefaultUser user = new DefaultUser("userToUpdate");
+        user.setCredentials("password");
+        User createdUser = userProvider.create(user).blockingGet();
+
+        DefaultUser updateUser = new DefaultUser("userToUpdate");
+        updateUser.setCredentials("password2");
+        userProvider.update(createdUser.getId(), updateUser).blockingGet();
+
+        TestObserver<User> testObserver = userProvider.findByUsername("userToUpdate").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertValue(u -> "password2".equals(updateUser.getCredentials()));
+    }
+
+    @Test
+    public void shouldNotUpdate_userNotFound() {
+        DefaultUser user = new DefaultUser("userToUpdate");
+        TestObserver testObserver = userProvider.update("unknown", user).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertError(UserNotFoundException.class);
+        testObserver.assertNoValues();
+    }
+
+    @Test
+    public void shouldDelete() {
+        DefaultUser user = new DefaultUser("userToDelete");
+        User createdUser = userProvider.create(user).blockingGet();
+        userProvider.delete(createdUser.getId()).blockingGet();
+
+        TestObserver<User> testObserver = userProvider.findByUsername("userToDelete").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoValues();
+    }
+
+    @Test
+    public void shouldNotDelete_userNotFound() {
+        TestObserver testObserver = userProvider.delete("unknown").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertError(UserNotFoundException.class);
+        testObserver.assertNoValues();
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/resources/container-license-acceptance.txt b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/resources/container-license-acceptance.txt
new file mode 100644
index 0000000000..8a704b9c06
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-jdbc/src/test/resources/container-license-acceptance.txt
@@ -0,0 +1,1 @@
+mcr.microsoft.com/mssql/server:2017-CU12