forked from Baeldung/spring-security-oauth
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
645 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<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> | ||
|
||
<groupId>com.baeldung</groupId> | ||
<artifactId>keycloak-custom-providers</artifactId> | ||
<version>0.1.0-SNAPSHOT</version> | ||
<packaging>jar</packaging> | ||
|
||
<parent> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-parent</artifactId> | ||
<version>2.2.6.RELEASE</version> | ||
<relativePath /> | ||
</parent> | ||
|
||
<dependencies> | ||
|
||
<!-- Embedded Keycloak sample --> | ||
<dependency> | ||
<groupId>com.baeldung</groupId> | ||
<artifactId>oauth-authorization-server</artifactId> | ||
<version>0.1.0-SNAPSHOT</version> | ||
</dependency> | ||
|
||
<!-- config properties processor --> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-configuration-processor</artifactId> | ||
<optional>true</optional> | ||
</dependency> | ||
|
||
<!-- test --> | ||
|
||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-test</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.h2database</groupId> | ||
<artifactId>h2</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
|
||
|
||
<dependency> | ||
<groupId>io.rest-assured</groupId> | ||
<artifactId>rest-assured</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<configuration> | ||
<excludes> | ||
<exclude>**/*LiveTest.java</exclude> | ||
</excludes> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<developers> | ||
<developer> | ||
<email>[email protected]</email> | ||
<name>Eugen Paraschiv</name> | ||
<url>https://github.com/eugenp</url> | ||
<id>eugenp</id> | ||
</developer> | ||
</developers> | ||
|
||
<properties> | ||
<!-- non-dependencies --> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<java.version>13</java.version> | ||
|
||
<keycloak.version>11.0.2</keycloak.version> | ||
|
||
<!-- these should be updated together with Keycloak --> | ||
<!-- check keycloak-dependencies-server-all effective pom --> | ||
<infinispan.version>10.1.8.Final</infinispan.version> | ||
<resteasy.version>3.12.1.Final</resteasy.version> | ||
</properties> | ||
|
||
</project> |
279 changes: 279 additions & 0 deletions
279
...om-providers/src/main/java/com/baeldung/auth/provider/user/CustomUserStorageProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
/** | ||
* | ||
*/ | ||
package com.baeldung.auth.provider.user; | ||
|
||
import java.sql.Connection; | ||
import java.sql.PreparedStatement; | ||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
import java.sql.Statement; | ||
import java.text.DateFormat; | ||
import java.text.SimpleDateFormat; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.keycloak.component.ComponentModel; | ||
import org.keycloak.credential.CredentialInput; | ||
import org.keycloak.credential.CredentialInputValidator; | ||
import org.keycloak.credential.CredentialModel; | ||
import org.keycloak.models.GroupModel; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.RealmModel; | ||
import org.keycloak.models.UserModel; | ||
import org.keycloak.models.credential.PasswordCredentialModel; | ||
import org.keycloak.storage.UserStorageProvider; | ||
import org.keycloak.storage.adapter.AbstractUserAdapter; | ||
import org.keycloak.storage.user.UserLookupProvider; | ||
import org.keycloak.storage.user.UserQueryProvider; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* @author Philippe | ||
* | ||
*/ | ||
public class CustomUserStorageProvider implements UserStorageProvider, UserLookupProvider, CredentialInputValidator, UserQueryProvider { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(CustomUserStorageProvider.class); | ||
private KeycloakSession ksession; | ||
private ComponentModel model; | ||
|
||
public CustomUserStorageProvider(KeycloakSession ksession, ComponentModel model) { | ||
this.ksession = ksession; | ||
this.model = model; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
log.info("[I30] close()"); | ||
} | ||
|
||
@Override | ||
public UserModel getUserById(String id, RealmModel realm) { | ||
log.info("[I35] getUserById({})",id); | ||
String[] parts = id.split(":"); | ||
if ( parts != null && parts.length == 3) { | ||
return getUserByUsername(parts[2],realm); | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public UserModel getUserByUsername(String username, RealmModel realm) { | ||
log.info("[I41] getUserByUsername({})",username); | ||
try ( Connection c = DbUtil.getConnection(this.model)) { | ||
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users where username = ?"); | ||
st.setString(1, username); | ||
st.execute(); | ||
ResultSet rs = st.getResultSet(); | ||
if ( rs.next()) { | ||
return mapUser(realm,rs); | ||
} | ||
else { | ||
return null; | ||
} | ||
} | ||
catch(SQLException ex) { | ||
throw new RuntimeException("Database error:" + ex.getMessage(),ex); | ||
} | ||
} | ||
|
||
@Override | ||
public UserModel getUserByEmail(String email, RealmModel realm) { | ||
log.info("[I48] getUserByEmail({})",email); | ||
try ( Connection c = DbUtil.getConnection(this.model)) { | ||
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users where email = ?"); | ||
st.setString(1, email); | ||
st.execute(); | ||
ResultSet rs = st.getResultSet(); | ||
if ( rs.next()) { | ||
return mapUser(realm,rs); | ||
} | ||
else { | ||
return null; | ||
} | ||
} | ||
catch(SQLException ex) { | ||
throw new RuntimeException("Database error:" + ex.getMessage(),ex); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean supportsCredentialType(String credentialType) { | ||
log.info("[I57] supportsCredentialType({})",credentialType); | ||
return PasswordCredentialModel.TYPE.endsWith(credentialType); | ||
} | ||
|
||
@Override | ||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { | ||
log.info("[I57] isConfiguredFor(realm={},user={},credentialType={})",realm.getName(), user.getUsername(), credentialType); | ||
// In our case, password is the only type of credential, so we allways return 'true' if | ||
// this is the credentialType | ||
return supportsCredentialType(credentialType); | ||
} | ||
|
||
@Override | ||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) { | ||
log.info("[I57] isValid(realm={},user={},credentialInput.type={})",realm.getName(), user.getUsername(), credentialInput.getType()); | ||
if( !this.supportsCredentialType(credentialInput.getType())) { | ||
return false; | ||
} | ||
String username = user.getUsername(); | ||
if ( username == null ) { | ||
// Fallback to id hack | ||
String[] idParts = user.getId().split(":"); | ||
if ( idParts != null && idParts.length == 3 ) { | ||
username = idParts[2]; | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
try ( Connection c = DbUtil.getConnection(this.model)) { | ||
PreparedStatement st = c.prepareStatement("select password from users where username = ?"); | ||
st.setString(1, username); | ||
st.execute(); | ||
ResultSet rs = st.getResultSet(); | ||
if ( rs.next()) { | ||
String pwd = rs.getString(1); | ||
return pwd.equals(credentialInput.getChallengeResponse()); | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
catch(SQLException ex) { | ||
throw new RuntimeException("Database error:" + ex.getMessage(),ex); | ||
} | ||
} | ||
|
||
// UserQueryProvider implementation | ||
|
||
@Override | ||
public int getUsersCount(RealmModel realm) { | ||
log.info("[I93] getUsersCount: realm={}", realm.getName() ); | ||
try ( Connection c = DbUtil.getConnection(this.model)) { | ||
Statement st = c.createStatement(); | ||
st.execute("select count(*) from users"); | ||
ResultSet rs = st.getResultSet(); | ||
rs.next(); | ||
return rs.getInt(1); | ||
} | ||
catch(SQLException ex) { | ||
throw new RuntimeException("Database error:" + ex.getMessage(),ex); | ||
} | ||
} | ||
|
||
@Override | ||
public List<UserModel> getUsers(RealmModel realm) { | ||
return getUsers(realm,0, 5000); // Keep a reasonable maxResults | ||
} | ||
|
||
@Override | ||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) { | ||
log.info("[I113] getUsers: realm={}", realm.getName()); | ||
|
||
try ( Connection c = DbUtil.getConnection(this.model)) { | ||
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users order by username limit ? offset ?"); | ||
st.setInt(1, maxResults); | ||
st.setInt(2, firstResult); | ||
st.execute(); | ||
ResultSet rs = st.getResultSet(); | ||
List<UserModel> users = new ArrayList<>(); | ||
while(rs.next()) { | ||
users.add(mapUser(realm,rs)); | ||
} | ||
return users; | ||
} | ||
catch(SQLException ex) { | ||
throw new RuntimeException("Database error:" + ex.getMessage(),ex); | ||
} | ||
} | ||
|
||
@Override | ||
public List<UserModel> searchForUser(String search, RealmModel realm) { | ||
return searchForUser(search,realm,0,5000); | ||
} | ||
|
||
@Override | ||
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { | ||
log.info("[I139] searchForUser: realm={}", realm.getName()); | ||
|
||
try ( Connection c = DbUtil.getConnection(this.model)) { | ||
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users where username like ? order by username limit ? offset ?"); | ||
st.setString(1, search); | ||
st.setInt(2, maxResults); | ||
st.setInt(3, firstResult); | ||
st.execute(); | ||
ResultSet rs = st.getResultSet(); | ||
List<UserModel> users = new ArrayList<>(); | ||
while(rs.next()) { | ||
users.add(mapUser(realm,rs)); | ||
} | ||
return users; | ||
} | ||
catch(SQLException ex) { | ||
throw new RuntimeException("Database error:" + ex.getMessage(),ex); | ||
} | ||
} | ||
|
||
@Override | ||
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) { | ||
return searchForUser(params,realm,0,5000); | ||
} | ||
|
||
@Override | ||
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) { | ||
return getUsers(realm, firstResult, maxResults); | ||
} | ||
|
||
@Override | ||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
@Override | ||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
@Override | ||
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
|
||
|
||
private UserModel mapUser(RealmModel realm, ResultSet rs) throws SQLException { | ||
|
||
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd"); | ||
CustomUser user = new CustomUser(ksession, realm, model, rs.getString("username")); | ||
// user.setEmail(rs.getString("email")); | ||
// user.setFirstName(rs.getString("firstName")); | ||
// user.setLastName(rs.getString("lastName")); | ||
// user.setAttribute("birthDate", Arrays.asList(fmt.format(rs.getDate("birthDate")))); | ||
|
||
return user; | ||
} | ||
|
||
private static class CustomUser extends AbstractUserAdapter { | ||
|
||
private String username; | ||
|
||
public CustomUser(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel,String username) { | ||
super(session, realm, storageProviderModel); | ||
this.username = username; | ||
} | ||
|
||
@Override | ||
public String getUsername() { | ||
return username; | ||
} | ||
|
||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
...ers/src/main/java/com/baeldung/auth/provider/user/CustomUserStorageProviderConstants.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.baeldung.auth.provider.user; | ||
|
||
public final class CustomUserStorageProviderConstants { | ||
public static final String CONFIG_KEY_JDBC_DRIVER = "jdbcDriver"; | ||
public static final String CONFIG_KEY_JDBC_URL = "jdbcUrl"; | ||
public static final String CONFIG_KEY_DB_USERNAME = "username"; | ||
public static final String CONFIG_KEY_DB_PASSWORD = "password"; | ||
public static final String CONFIG_KEY_VALIDATION_QUERY = "validationQuery"; | ||
} |
Oops, something went wrong.