7.7. 添加/删除用户和查询功能接口
我们没有通过我们的示例完成的一个操作是允许它添加和删除用户或更改密码。在我们的示例中定义的用户也可以在管理控制台中查询或查看。要添加这些增强功能,我们的示例供应商必须实施 UserQueryMethodsProvider
(或 UserQueryProvider
)和 UserRegistrationProvider
接口。
7.7.1. 实现 UserRegistrationProvider
使用这个流程实现从特定存储中添加和删除用户,我们首先需要将属性文件保存到磁盘。
PropertyFileUserStorageProvider
public void save() { String path = model.getConfig().getFirst("path"); path = EnvUtil.replace(path); try { FileOutputStream fos = new FileOutputStream(path); properties.store(fos, ""); fos.close(); } catch (IOException e) { throw new RuntimeException(e); } }
public void save() {
String path = model.getConfig().getFirst("path");
path = EnvUtil.replace(path);
try {
FileOutputStream fos = new FileOutputStream(path);
properties.store(fos, "");
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
然后,addUser ()
和 removeUser ()
方法的实现变得简单。
PropertyFileUserStorageProvider
public static final String UNSET_PASSWORD="#$!-UNSET-PASSWORD"; @Override public UserModel addUser(RealmModel realm, String username) { synchronized (properties) { properties.setProperty(username, UNSET_PASSWORD); save(); } return createAdapter(realm, username); } @Override public boolean removeUser(RealmModel realm, UserModel user) { synchronized (properties) { if (properties.remove(user.getUsername()) == null) return false; save(); return true; } }
public static final String UNSET_PASSWORD="#$!-UNSET-PASSWORD";
@Override
public UserModel addUser(RealmModel realm, String username) {
synchronized (properties) {
properties.setProperty(username, UNSET_PASSWORD);
save();
}
return createAdapter(realm, username);
}
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
synchronized (properties) {
if (properties.remove(user.getUsername()) == null) return false;
save();
return true;
}
}
请注意,在添加用户时,我们将属性映射的密码值设置为 UNSET_PASSWORD
。我们这样做,因为我们没有属性值中属性的 null 值。我们还必须修改 CredentialInputValidator
方法以反映这一点。
如果提供程序实现了 UserRegistrationProvider
接口,则将调用 addUser ()
方法。如果您的供应商有一个配置开关来为用户关闭,请从此方法返回 null
将跳过供应商并调用下一个供应商。
PropertyFileUserStorageProvider
@Override public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false; UserCredentialModel cred = (UserCredentialModel)input; String password = properties.getProperty(user.getUsername()); if (password == null || UNSET_PASSWORD.equals(password)) return false; return password.equals(cred.getValue()); }
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
UserCredentialModel cred = (UserCredentialModel)input;
String password = properties.getProperty(user.getUsername());
if (password == null || UNSET_PASSWORD.equals(password)) return false;
return password.equals(cred.getValue());
}
由于我们现在可以保存我们的属性文件,因此允许密码更新也有意义。
PropertyFileUserStorageProvider
@Override public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { if (!(input instanceof UserCredentialModel)) return false; if (!input.getType().equals(PasswordCredentialModel.TYPE)) return false; UserCredentialModel cred = (UserCredentialModel)input; synchronized (properties) { properties.setProperty(user.getUsername(), cred.getValue()); save(); } return true; }
@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!(input instanceof UserCredentialModel)) return false;
if (!input.getType().equals(PasswordCredentialModel.TYPE)) return false;
UserCredentialModel cred = (UserCredentialModel)input;
synchronized (properties) {
properties.setProperty(user.getUsername(), cred.getValue());
save();
}
return true;
}
现在,我们还可以实施禁用密码。
PropertyFileUserStorageProvider
@Override public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { if (!credentialType.equals(PasswordCredentialModel.TYPE)) return; synchronized (properties) { properties.setProperty(user.getUsername(), UNSET_PASSWORD); save(); } } private static final Set<String> disableableTypes = new HashSet<>(); static { disableableTypes.add(PasswordCredentialModel.TYPE); } @Override public Stream<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { return disableableTypes.stream(); }
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
if (!credentialType.equals(PasswordCredentialModel.TYPE)) return;
synchronized (properties) {
properties.setProperty(user.getUsername(), UNSET_PASSWORD);
save();
}
}
private static final Set<String> disableableTypes = new HashSet<>();
static {
disableableTypes.add(PasswordCredentialModel.TYPE);
}
@Override
public Stream<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
return disableableTypes.stream();
}
实施这些方法后,您现在可以更改并禁用管理控制台中用户的密码。
7.7.2. 实现 UserQueryProvider
UserQueryProvider
是 UserQueryMethodsProvider
和 UserCountMethodsProvider
的组合。如果没有实现 UserQueryMethodsProvider
,则管理控制台将无法查看和管理我们示例供应商加载的用户。我们来了解如何实施此接口。
PropertyFileUserStorageProvider
@Override public int getUsersCount(RealmModel realm) { return properties.size(); } @Override public Stream<UserModel> searchForUserStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) { Predicate<String> predicate = "*".equals(search) ? username -> true : username -> username.contains(search); return properties.keySet().stream() .map(String.class::cast) .filter(predicate) .skip(firstResult) .map(username -> getUserByUsername(realm, username)) .limit(maxResults); }
@Override
public int getUsersCount(RealmModel realm) {
return properties.size();
}
@Override
public Stream<UserModel> searchForUserStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) {
Predicate<String> predicate = "*".equals(search) ? username -> true : username -> username.contains(search);
return properties.keySet().stream()
.map(String.class::cast)
.filter(predicate)
.skip(firstResult)
.map(username -> getUserByUsername(realm, username))
.limit(maxResults);
}
searchForUserStream ()
的第一个声明采用 String
参数。在本例中,参数代表您要搜索的用户名。此字符串可以是子字符串,它解释了执行搜索时 String.contains ()
方法的选择。请注意,使用 *
表示请求所有用户的列表。该方法迭代属性文件的关键集合,委派为 getUserByUsername ()
来加载用户。请注意,我们根据 firstResult
和 maxResults
参数索引此调用。如果您的外部存储不支持分页,则必须执行类似的逻辑。
PropertyFileUserStorageProvider
@Override public Stream<UserModel> searchForUserStream(RealmModel realm, Map<String, String> params, Integer firstResult, Integer maxResults) { // only support searching by username String usernameSearchString = params.get("username"); if (usernameSearchString != null) return searchForUserStream(realm, usernameSearchString, firstResult, maxResults); // if we are not searching by username, return all users return searchForUserStream(realm, "*", firstResult, maxResults); }
@Override
public Stream<UserModel> searchForUserStream(RealmModel realm, Map<String, String> params, Integer firstResult, Integer maxResults) {
// only support searching by username
String usernameSearchString = params.get("username");
if (usernameSearchString != null)
return searchForUserStream(realm, usernameSearchString, firstResult, maxResults);
// if we are not searching by username, return all users
return searchForUserStream(realm, "*", firstResult, maxResults);
}
使用 Map
参数的 searchForUserStream ()
方法可以根据名字、姓氏、用户名和电子邮件搜索用户。仅存储用户名,因此搜索仅基于用户名,除了 Map
参数不包含 username
属性时除外。在这种情况下,所有用户都会被返回。在这种情况下,使用 searchForUserStream (realm, search, firstResult, maxResults)
。
PropertyFileUserStorageProvider
@Override public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) { return Stream.empty(); } @Override public Stream<UserModel> searchForUserByUserAttributeStream(RealmModel realm, String attrName, String attrValue) { return Stream.empty(); }
@Override
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
return Stream.empty();
}
@Override
public Stream<UserModel> searchForUserByUserAttributeStream(RealmModel realm, String attrName, String attrValue) {
return Stream.empty();
}
组或属性不会被存储,因此其他方法会返回空流。