转载于:https://www.ycblog.top/article?articleId=399&pageNum=1
Keycloak 是一个针对现代应用程序和服务的开源身份和访问管理解决方案。Keycloak 支持单点登录(Single-Sign On),因此服务可以通过 OpenID Connect、OAuth 2.0 等协议对接 Keycloak。同时 Keycloak 也支持集成不同的身份认证服务,例如 Github、Google 和 Facebook 等。对于keycloak其他说明可查看官方文档:https://www.keycloak.org/
1、准备用户表,这些字段是必须的,不过字段名可以自定义,会在自定义的SPI中固定字段名,查询数据库的字段需要映射到实体的字段
2、添加pom依赖,打包需要同时把引入的依赖一起打包,下面是整个pom文件
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<repositories>
<repository>
<id>jboss</id>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<keycloak.version>16.1.1</keycloak.version>
<version.compiler.maven.plugin>3.5.1</version.compiler.maven.plugin>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.4.3.Final</version>
<scope>provided</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<!-- JDBC 工具包 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
<build>
<finalName>bpm-auth</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass/>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
3、自定义用户表实体 BaseUserEntity(不一定和数据库用户表进行对应)
public class BaseUserEntity {
private String id;
private String username;
private String password;
private String email;
private String phone;
private String nickname;
//租户id,可能存在租户隔离private String tenantId;
.... get set 方法
}
4、创建用户字段适配器 BaseUserAdapter,可以添加自定义的属性字段,有时候我们需要传递一些额外的字段到keycloak中,可以在这里进行处理转换
public class BaseUserAdapter extends AbstractUserAdapterFederatedStorage {
private static final Logger logger = Logger.getLogger(BaseUserAdapter.class);
protected BaseUserEntity entity;
protected String keycloakId;
public BaseUserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, BaseUserEntity entity) {
super(session, realm, model);
this.entity = entity;
keycloakId = StorageId.keycloakId(model, entity.getId());
}
public void setPassword(String password) {
entity.setPassword(password);
}
public String getPassword() {
return entity.getPassword();
}
@Override
public String getUsername() {
return entity.getUsername();
}
@Override
public void setUsername(String username) {
entity.setUsername(username);
}
@Override
public void setEmail(String email) {
entity.setEmail(email);
}
@Override
public String getEmail() {
return entity.getEmail();
}
@Override
public String getId() {
return keycloakId;
}
@Override
protected boolean appendDefaultRolesToRoleMappings() {
return false;
}
@Override
public String getFirstName() {
return entity.getNickname();
}
@Override
public void setFirstName(String firstName) {
entity.setNickname(firstName);
}
@Override
public String getLastName() {
return entity.getNickname();
}
@Override
public void setLastName(String lastName) {
entity.setNickname(lastName);
}
@Override
protected Set<RoleModel> getRoleMappingsInternal() {
HashSet<RoleModel> roleModels = new HashSet<>();
logger.infof("============getRoleMappingsInternal=============");
if(entity.getRoleId() != null && !entity.getRoleId().trim().isEmpty()){
String roleName = entity.getRoleId() + "::" + entity.getRoleName();
RoleModel role = realm.getRole(roleName);
if (role == null) {
role = realm.addRole(roleName);
}
roleModels.add(role);
}
return roleModels;
}
@Override
public void setSingleAttribute(String name, String value) {
logger.infof("设置单个属性:%s", name);
if ("tenantId".equals(name)) {
entity.setTenantId(value);
} else {
super.setSingleAttribute(name, value);
}
}
@Override
public void removeAttribute(String name) {
if ("tenantId".equals(name)) {
entity.setTenantId(null);
} else {
super.removeAttribute(name);
}
}
@Override
public void setAttribute(String name, List<String> values) {
logger.infof("设置属性:%s", name);
if ("tenantId".equals(name)) {
entity.setTenantId(name);
} else {
super.setAttribute(name, values);
}
}
@Override
public String getFirstAttribute(String name) {
if ("tenantId".equals(name)) {
return entity.getTenantId();
} else if ("firstName".equals(name)) {
return entity.getNickname();
} else {
return super.getFirstAttribute(name);
}
}
@Override
public Map<String, List<String>> getAttributes() {
Map<String, List<String>> attrs = super.getAttributes();
MultivaluedHashMap<String, String> all = new MultivaluedHashMap<>();
all.putAll(attrs);
all.add("tenantId", entity.getTenantId());
all.add("firstName", entity.getNickname());
return all;
}
@Override
public List<String> getAttribute(String name) {
logger.infof("获取属性:%s, 值 %s", name, super.getAttribute(name));
if ("tenantId".equals(name)) {
List<String> tenantId = new LinkedList<>();
tenantId.add(entity.getTenantId());
return tenantId;
} else if ("firstName".equals(name)) {
List<String> firstName = new LinkedList<>();
firstName.add(entity.getNickname());
return firstName;
} else {
return super.getAttribute(name);
}
}
}
5、创建用户提供器BaseUserStorageProvider
该类实现了以下几个接口,这几个接口的含义如下:
1、UserStorageProvider, 自定义用户必须要实现的接口
2、UserLookupProvider, 实现后,可以根据userId/username从你自己的数据中查询用户数据
3、CredentialInputValidator, 实现后,可以更新密码
4、UserRegistrationProvider, 实现后,可以往自己数据库中增加删除修改用户数据
5、UserQueryProvider 实现后,可以从自己数据库中查询用户
以上几个接口的具体详细介绍可以到官网上查看,我们这里就简单说下本次用到的。
继承以上这些接口后,我们需要一步步具体的实现这些接口
/**
* keycloak用户连接器
* UserStorageProvider 自定义的StorageProvider必须要实现的接口
* UserLookupProvider 实现后,可以根据userId/username从你自己的数据中查询用户数据
* UserRegistrationProvider 实现后,可以往自己数据库中增加删除修改用户数据
* CredentialInputUpdater 实现后,可以更新密码
* CredentialInputValidator 验证密码的逻辑
* UserQueryProvider 从自己数据库中查询用户
*
* @author yancao
*/
public class BaseUserStorageProvider implementsUserStorageProvider,
UserLookupProvider,
CredentialInputValidator,
UserQueryProvider {
private static final Logger logger = Logger.getLogger(BaseUserStorageProvider.class);
protected ComponentModel model;
protected KeycloakSession session;
private final BaseUserStorageRepository userStorageDao;
public BaseUserStorageProvider(ComponentModel model,
KeycloakSession session,
String tableName,
String tableField,
String userNameField,
String emailField) {
this.model = model;
this.session = session;
this.userStorageDao = new BaseUserStorageRepository(model, tableName, tableField, userNameField, emailField);
}
@Override
public boolean supportsCredentialType(String credentialType) {
logger.infof("=============> supportsCredentialType# %s", credentialType);
return CredentialModel.PASSWORD.equals(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
logger.infof("=============> isConfiguredFor# %s", credentialType);
return supportsCredentialType(credentialType) && getPassword(user) != null;
}
/**
* 验证用户是否正确
*
* @param realm realm
* @param user user
* @param input input
* @return boolean
*/
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
logger.infof("=============> isValid");
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) {
return false;
}
UserCredentialModel cred = (UserCredentialModel) input;
logger.infof("=============> inputPassWord# %s", cred.getValue());
logger.infof("=============> inputUserName# %s", user.getUsername());
//模拟实现shiro sha加密,我的用户系统原来使用的是shiro,这里你根据自己的情况进行调整既可//String shaPwd = new FakeShiroSHAUtil(inputPwd, userExt.getSalt()).toString();
String password = getPassword(user);
logger.infof("=============> realPassWord# %s", password);
return password != null && password.equals(cred.getValue());
}
/**
* 获取密码
*
* @param user user
* @return String
*/
public String getPassword(UserModel user) {
String password;
if (user instanceof BaseUserAdapter) {
//本地存储获取
password = ((BaseUserAdapter) user).getPassword();
} else {
//从外部数据库查询出BaseUserEntity userExt = userStorageDao.getByUserName(user.getUsername());
logger.infof(userExt.toString());
password = userExt.getPassword();
}
return password;
}
@Override
public void close() {
}
/**
* 通过用户ID查询用户
*
* @param s s
* @param realmModel realmModel
* @return UserModel
*/
@Override
public UserModel getUserById(String s, RealmModel realmModel) {
logger.info("==============> getUserById:" + s);
//注意:要用此方法获取到外部ID,直接传过来的ID是keycloak二次处理过的,需要处理为实际的idString externalId = StorageId.externalId(s);
logger.infof(" 外部ID:id->%s,用户id:user_id->%s", s, externalId);
//从数据库查询BaseUserEntity baseUser = userStorageDao.getById(externalId);if (null == baseUser) {
logger.info("根据用户id为找到用户信息: " + externalId);
return null;
}
return new BaseUserAdapter(session, realmModel, model, baseUser);
}
@Override
public UserModel getUserByUsername(String username, RealmModel realmModel) {
logger.info("==============> getUserByUsername: " + username);
//从数据库查询BaseUserEntity baseUser = userStorageDao.getByUserName(username);if (null == baseUser) {
logger.info("根据名称未找到用户信息: " + username);
return null;
}
return new BaseUserAdapter(session, realmModel, model, baseUser);
}
@Override
public UserModel getUserByEmail(String email, RealmModel realmModel) {
logger.info("==============> getUserByEmail: " + email);
//从数据库查询BaseUserEntity baseUser = userStorageDao.getByEmail(email);if (null == baseUser) {
logger.info("could not find username: " + email);
return null;
}
return new BaseUserAdapter(session, realmModel, model, baseUser);
}
/**
* 查询用户总数
*
* @return int
*/
@Override
public int getUsersCount(RealmModel realm) {
//从数据库查询
logger.info("#################getUserCount############");
return userStorageDao.count();
}
/**
* 查询用户列表
*/
@Override
public List<UserModel> getUsers(RealmModel realmModel) {
logger.error("#################getUsers");
List<BaseUserEntity> baseUserList = userStorageDao.getUserList();
return BaseUserStorageRepository.baseUserToUserModel(baseUserList, session, realmModel, model);
}
/**
* 分页查询用户列表
*/
@Override
public List<UserModel> getUsers(RealmModel realmModel, int firstResult, int maxResults) {
logger.error("#################getUsers-page");
List<BaseUserEntity> baseUserList = userStorageDao.getUserList(firstResult, maxResults);
return BaseUserStorageRepository.baseUserToUserModel(baseUserList, session, realmModel, model);
}
/**
* 根据搜索框查询用户,搜索框默认模糊查询用户名和email
*/
@Override
public List<UserModel> searchForUser(String s, RealmModel realmModel) {
logger.info("#################searchForUser");
List<BaseUserEntity> baseUserList = userStorageDao.getUserBySearch(s);
return BaseUserStorageRepository.baseUserToUserModel(baseUserList, session, realmModel, model);
}
/**
* 分页搜索用户,搜索框默认模糊查询用户名和email
*
* @param s s
* @param realmModel realmModel
* @param firstResult firstResult
* @param maxResults maxResults
* @return List
*/
@Override
public List<UserModel> searchForUser(String s, RealmModel realmModel, int firstResult, int maxResults) {
logger.infof("query all user from database page, %d, %d", firstResult, maxResults);
List<BaseUserEntity> baseUserList = userStorageDao.getUserBySearch(s, firstResult, maxResults);
return BaseUserStorageRepository.baseUserToUserModel(baseUserList, session, realmModel, model);
}
/**
* 根据查询条件查询用户
*/
@Override
public List<UserModel> searchForUser(Map<String, String> map, RealmModel realmModel) {
logger.infof("#################searchForUser2");
return searchForUser(map, realmModel, 0, 2000);
}
/**
* 用户分页查询,在后台控制台的查询中会调用此方法
*
* @param map map
* @param realmModel realmModel
* @param firstResult firstResult
* @param maxResults maxResults
* @return List
*/
@Override
public List<UserModel> searchForUser(Map<String, String> map, RealmModel realmModel, int firstResult, int maxResults) {
logger.infof("this params map is unuseful !!! query all user from database page, %d, %d", firstResult, maxResults);
List<BaseUserEntity> baseUserList = userStorageDao.getUserList(firstResult, maxResults);
return BaseUserStorageRepository.baseUserToUserModel(baseUserList, session, realmModel, model);
}
@Override
public List<UserModel> getGroupMembers(RealmModel realmModel, GroupModel groupModel) {
return null;
}
@Override
public List<UserModel> getGroupMembers(RealmModel realmModel, GroupModel groupModel, int i, int i1) {
return null;
}
@Override
public List<UserModel> searchForUserByUserAttribute(String s, String s1, RealmModel realmModel) {
return null;
}
}
6、创建提供者工厂,在keycloak管理界面添加用户联盟时的自定义的配置信息,主要是数据库连接
public class BaseUserStorageProviderFactory implements UserStorageProviderFactory<BaseUserStorageProvider> {
private static final Logger logger = Logger.getLogger(BaseUserStorageProviderFactory.class);
protected final List<ProviderConfigProperty> configMetadata;
public BaseUserStorageProviderFactory() {
logger.info("初始化用户提供器");
//这些配置对应控制台的配置页面
configMetadata = ProviderConfigurationBuilder.create()
.property()
.name(DbConnectionPool.CONFIG_KEY_JDBC_DRIVER)
.label("JDBC驱动")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("com.mysql.cj.jdbc.Driver")
.helpText("完整的JDBC驱动包名称")
.add()
.property()
.name(DbConnectionPool.CONFIG_KEY_JDBC_URL)
.label("JDBC URL")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("jdbc:mysql://")
.helpText("JDBC URL连接串")
.add()
.property()
.name(DbConnectionPool.CONFIG_KEY_DB_USERNAME)
.label("账号")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("数据库用户名")
.add()
.property()
.name(DbConnectionPool.CONFIG_KEY_DB_PASSWORD)
.label("密码")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("数据库连接密码")
.secret(true)
.add()
.property()
.name(DbConnectionPool.TABLE_NAME)
.label("用户表名")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("数据库表名,关联的用户表")
.add()
.property()
.name(DbConnectionPool.SELECT_TABLE_FIELD)
.label("查询字段")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("数据库查询字段,关联到接收数据字段,必须和实体中的字段对应,实体字段为:" +
"主键:id," +
"登录名:username," +
"密码:password," +
"邮箱:email," +
"电话:phone," +
"昵称:nickname," +
"租户:tenantId," +
"角色:roleId;" +
"例如:id as id,login_name as username,login_pwd as password,email as email,mobile as phone,role_id as roleId")
.add()
.property()
.name(DbConnectionPool.USER_NAME_TABLE_FIELD)
.label("用户名字段")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("用户名字段,用于登录根据用户名查询用户的字段名")
.add()
.property()
.name(DbConnectionPool.EMAIL_TABLE_FIELD)
.label("邮箱字段")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("邮箱字段,用于登录根据邮箱查询用户的字段名")
.add()
.build();
}
/**
* 初始化
*
* @param config config
*/
@Override
public void init(Config.Scope config) {
}
@Override
public BaseUserStorageProvider create(KeycloakSession session, ComponentModel model) {
try {
logger.infof("创建用户提供器,开始加载数据库连接池... %s", model.getConfig().getFirst(DbConnectionPool.CONFIG_KEY_JDBC_URL));
//初始化数据库连接
DbConnectionPool.getInstance().initDb(model);
return new BaseUserStorageProvider(model, session,
model.getConfig().getFirst(DbConnectionPool.TABLE_NAME),
model.getConfig().getFirst(DbConnectionPool.SELECT_TABLE_FIELD),
model.getConfig().getFirst(DbConnectionPool.USER_NAME_TABLE_FIELD),
model.getConfig().getFirst(DbConnectionPool.EMAIL_TABLE_FIELD),
model.getConfig().getFirst(DbConnectionPool.ROLE_TABLE_NAME));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 提供器名称,在控制台中选择的时候显示
*/
@Override
public String getId() {
return "custom-user-provider";
}
@Override
public String getHelpText() {
return "自定义用户提供器";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configMetadata;
}
/**
* 验证数据库连接是否正常
*
* @param session session
* @param realm realm
* @param config config
* @throws ComponentValidationException ComponentValidationException
*/
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
System.out.println("#####################validateConfiguration#############" + config.getId());
//初始化数据库连接
MultivaluedHashMap<String, String> configMap = config.getConfig();
DataSource dataSource = new HikariConf().init(configMap);
try (Connection c = dataSource.getConnection()) {
if (!c.isValid(20000)) {
throw new ComponentValidationException("连接数据库超时");
}
} catch (Exception e) {
e.printStackTrace();
throw new ComponentValidationException("无法校验数据库连接", e);
}
}
}
7、添加插件的配置
要让插件正常使用,我们还需要添加一个配置文件,在项目的resources路径下,新建
META-INF\services\org.keycloak.storage.UserStorageProviderFactory
在org.keycloak.storage.UserStorageProviderFactory文件中写入刚才创建的入口factory类的路径
比如我们的BaseUserStorageProviderFactory,带完整包名
com.wishare.auth.provider.BaseUserStorageProviderFactory
8、Dao层
public class BaseUserStorageRepository {
private static final Logger logger = Logger.getLogger(BaseUserStorageRepository.class);
private final ComponentModel model;
private final String tableName;
private final String selectTableField;
private final String userNameField;
private final String emailField;
public BaseUserStorageRepository(ComponentModel model,
String tableName,
String selectTableField,
String userNameField,
String emailField) {
this.model = model;
this.tableName = tableName;
this.selectTableField = selectTableField;
this.userNameField = userNameField;
this.emailField = emailField;
}
/**
* 查询用户
*
* @param s s
* @return BaseUserEntity
*/
public BaseUserEntity getByUserName(String s) {
logger.infof("根据用户名查询:%s", s);
String getUserByUsername = "select "+selectTableField+" from " + tableName + " where "+userNameField+" = ?";
return queryOne(getUserByUsername, s);
}
/**
* 根据ID查询
*
* @param id id
* @return BaseUserEntity
*/
public BaseUserEntity getById(String id) {
logger.infof("根据ID查询:%s", id);
String getUserById = "select "+selectTableField+" from " + tableName + " where id = ?";
return queryOne(getUserById, id);
}
/**
* 查询总数
*
* @return int
*/
public int count() {
try (Connection ignored = DbConnectionPool.getInstance().getConnection(model.getId())) {
String getUserCount = "select count(*) from " + tableName;
Long count = DbConnectionPool.getInstance().getQueryRunner(model.getId()).query(getUserCount, new ScalarHandler<>());
logger.infof("用户总数 : %d", count);
return Math.toIntExact(count);
} catch (SQLException e) {
e.printStackTrace();
return 0;
}
}
/**
* 查询用户
*
* @param s s
* @return BaseUserEntity
*/
public BaseUserEntity getByEmail(String s) {
logger.infof("根据邮箱查询:%s", s);
String getUserByEmail = "select "+selectTableField+" from " + tableName + " where "+emailField+" = ?";
return queryOne(getUserByEmail, s);
}
/**
* 查询用户列表
*
* @return List
*/
public List<BaseUserEntity> getUserList() {
logger.infof("查询用户列表");
String getAllUser = "select "+selectTableField+" from " + tableName;
return queryList(getAllUser);
}
/**
* 查询用户列表
*
* @return List
*/
public List<BaseUserEntity> getUserList(int start, int offset) {
logger.infof("分页查询用户");
String getUserByPage = "select "+selectTableField+" from " + tableName + " limit ?,?";
return queryList(getUserByPage, start, offset);
}
/**
* 查询用户列表
*
* @return List
*/
public List<BaseUserEntity> getUserBySearch(String key) {
logger.infof("用户名或邮箱查询:%s", key);
String getUserByUsernameOrEmail = "select "+selectTableField+" from " + tableName + " where "+userNameField+" = ? or "+emailField+" = ?";
return queryList(getUserByUsernameOrEmail, key,key);
}
/**
* 查询用户列表
*
* @return List
*/
public List<BaseUserEntity> getUserBySearch(String key, int start, int offset) {
logger.infof("用户名或邮箱分页查询:%s, %d, %d", key, start, offset);
String getUserByUsernameOrEmailPage = "select "+selectTableField+" from " + tableName + " where "+userNameField+" = ? or "+emailField+" = ? limit ?,?";
return queryList(getUserByUsernameOrEmailPage, key,key, start, offset);
}
/**
* 查询
*
* @return BaseUserEntity
*/
protected BaseUserEntity queryOne(String sql, Object... params) {
try (Connection ignored = DbConnectionPool.getInstance().getConnection(model.getId())) {
//开启驼峰映射BeanProcessor bean = new GenerousBeanProcessor();RowProcessor processor = new BasicRowProcessor(bean);//从数据库查询return DbConnectionPool.getInstance().getQueryRunner(model.getId()).query(sql, new BeanHandler<>(BaseUserEntity.class,processor), params);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
/**
* 查询列表
*
* @param sql sql
* @param params params
* @return List
*/
protected List<BaseUserEntity> queryList(String sql, Object... params) {
try (Connection ignored = DbConnectionPool.getInstance().getConnection(model.getId())) {
//开启驼峰映射BeanProcessor bean = new GenerousBeanProcessor();RowProcessor processor = new BasicRowProcessor(bean);//从数据库查询return DbConnectionPool.getInstance().getQueryRunner(model.getId()).query(sql, new BeanListHandler<>(BaseUserEntity.class,processor), params);
} catch (SQLException e) {
e.printStackTrace();
return Collections.emptyList();
}
}
/**
* 用户对象转keycloak对象
*
* @param baseUserList baseUserList
* @param session session
* @param realmModel realmModel
* @param model model
* @return List
*/
public static List<UserModel> baseUserToUserModel(List<BaseUserEntity> baseUserList, KeycloakSession session, RealmModel realmModel, ComponentModel model) {
if (null == baseUserList || baseUserList.size() == 0) {
return Collections.emptyList();
}
List<UserModel> allUser = new ArrayList<>();
baseUserList.forEach(b ->
allUser.add(new BaseUserAdapter(session, realmModel, model, b))
);
return allUser;
}
}
9、数据库连接工具
/**
* 统一数据库连接工具
*
* @author ChangSir
*/
public class DbConnectionPool {
private static final Logger log = Logger.getLogger(DbConnectionPool.class);
/**
* jdbc
*/public static final String CONFIG_KEY_JDBC_DRIVER = "CONFIG_KEY_JDBC_DRIVER";
public static final String CONFIG_KEY_JDBC_URL = "CONFIG_KEY_JDBC_URL";
public static final String CONFIG_KEY_DB_USERNAME = "CONFIG_KEY_DB_USERNAME";
public static final String CONFIG_KEY_DB_PASSWORD = "CONFIG_KEY_DB_PASSWORD";
public static final String TABLE_NAME = "TABLE_NAME";
public static final String SELECT_TABLE_FIELD = "SELECT_TABLE_FIELD";
public static final String USER_NAME_TABLE_FIELD = "USER_NAME_TABLE_FIELD";
public static final String EMAIL_TABLE_FIELD = "EMAIL_TABLE_FIELD";
public static final String ROLE_TABLE_NAME = "ROLE_TABLE_NAME";
/**
* 数据源配置
*/private ConcurrentHashMap<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
private static class DbConnectionHolder {
private static DbConnectionPool dbConnectionPool = new DbConnectionPool();
}
private DbConnectionPool() {
}
/**
* 单例模式实现
*
* @return DBConnectionPool
*/
public static synchronized DbConnectionPool getInstance() {
return DbConnectionHolder.dbConnectionPool;
}
/**
* 设置Db
*
* @param model model
*/
public void initDb(ComponentModel model) {
String dataId = model.getId();
MultivaluedHashMap<String, String> configMap = model.getConfig();
log.infof("init hikariCP pool... %s", dataId);
DataSource dataSource = dataSourceMap.get(dataId);
if (null == dataSource) {
log.infof("创建datasource");
dataSourceMap.put(dataId, new HikariConf().init(configMap));
}
}
/**
* 从连接池中获取链接
*
* @return Connection
*/
public synchronized Connection getConnection(String id) {
try {
return dataSourceMap.get(id).getConnection();
} catch (SQLException e) {
log.debug("获取连接失败:" + e.getMessage());
return null;
}
}
/**
* 获取query
*
* @return QueryRunner
*/
public QueryRunner getQueryRunner(String id) {
return new QueryRunner(dataSourceMap.get(id));
}
}
10、连接池配置
/**
* HikariCP数据库连接
*
* @author ChangSir
*/
public class HikariConf {
private static final Logger log = Logger.getLogger(HikariConf.class);
public DataSource init(MultivaluedHashMap<String, String> configMap) {
log.infof("init HikariCP");
HikariConfig config = new HikariConfig();
config.setDriverClassName(configMap.getFirst(DbConnectionPool.CONFIG_KEY_JDBC_DRIVER));
config.setJdbcUrl(configMap.getFirst(DbConnectionPool.CONFIG_KEY_JDBC_URL));
config.setUsername(configMap.getFirst(DbConnectionPool.CONFIG_KEY_DB_USERNAME));
config.setPassword(configMap.getFirst(DbConnectionPool.CONFIG_KEY_DB_PASSWORD));
//是否自定义配置,为true时下面两个参数才生效
config.addDataSourceProperty("cachePrepStmts", true);
//连接池大小默认25,官方推荐250-500
config.addDataSourceProperty("prepStmtCacheSize", 10);
//单条语句最大长度默认256,官方推荐2048
config.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
//新版本MySQL支持服务器端准备,开启能够得到显著性能提升
config.addDataSourceProperty("useServerPrepStmts", true);
config.addDataSourceProperty("useLocalSessionState", true);
config.addDataSourceProperty("useLocalTransactionState", true);
config.addDataSourceProperty("rewriteBatchedStatements", true);
config.addDataSourceProperty("cacheResultSetMetadata", true);
config.addDataSourceProperty("cacheServerConfiguration", true);
config.addDataSourceProperty("elideSetAutoCommits", true);
config.addDataSourceProperty("maintainTimeStats", false);
config.setConnectionTestQuery("SELECT 1 FROM DUAL");
//池中最大链接数量 一个4核,1块硬盘的服务器,连接数 = (4 * 2) + 1 = 9,凑个整数,10就可以了
config.setMaximumPoolSize(4);
//等待连接池分配连接的最大时长(毫秒)4s,超过这个时长还没可用的连接则发生SQLException, 缺省:30秒
config.setConnectionTimeout(4000);
//一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟
config.setIdleTimeout(60000);
/*
* 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,
* 参考MySQL wait_timeout参数(show variables like '%timeout%';)
*/
config.setMaxLifetime(300000);
return new HikariDataSource(config);
}
}
11、打包部署
现在回到IDEA中,将我们刚才创建的项目打包成jar文件,然后找打keycloak的程序路径,将jar文件放到对应文件夹下,运行keycloak程序
12、启动之后配置用户联盟
如果keycloak启动正常,打开控制台,点击左侧的 用户联合菜单,右侧的下拉列表中就能看到我们刚才添加的提供器,名字为 custom-user-provider
添加数据库配置,每一项都有说明,配置后进行保存,会校验数据库连接
查看效果,点击用户然后查看所有能够查看到数据库的用户,然后就能用数据库的用户进行登录了!!!
如果登录失败可以看控制台的日志,找到具体错误进行处理!!