7.6. 사용자 및 쿼리 기능 인터페이스 추가/제거

예제에 포함되지 않은 한 가지는 사용자를 추가 및 제거하거나 암호를 변경할 수 있도록 허용하는 것입니다. 예제에 정의된 사용자도 Admin Console에서 쿼리하거나 볼 수 없습니다. 이러한 향상된 기능을 추가하려면 예제 공급자가 UserQueryProviderUserRegistrationProvider 인터페이스를 구현해야 합니다.

7.6.1. Implementing 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);
        }
    }

그런 다음, 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;
        }
    }

사용자를 추가할 때 속성 맵의 암호 값을 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());
    }

이제 속성 파일을 저장할 수 있으므로 암호 업데이트를 허용하는 것도 좋습니다.

PropertyFileUserStorageProvider

    @Override
    public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
        if (!(input instanceof UserCredentialModel)) return false;
        if (!input.getType().equals(CredentialModel.PASSWORD)) 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(CredentialModel.PASSWORD)) return;
        synchronized (properties) {
            properties.setProperty(user.getUsername(), UNSET_PASSWORD);
            save();
        }

    }

    private static final Set<String> disableableTypes = new HashSet<>();

    static {
        disableableTypes.add(CredentialModel.PASSWORD);
    }

    @Override
    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {

        return disableableTypes;
    }

이러한 방법을 구현하면 Admin Console에서 사용자의 암호를 변경하고 비활성화할 수 있습니다.

7.6.2. UserQueryProvider 구현

UserQueryProvider 를 구현하지 않으면 관리 콘솔은 예제 공급자가 로드한 사용자를 보고 관리할 수 없습니다. 이 인터페이스 구현을 살펴보겠습니다.

PropertyFileUserStorageProvider

    @Override
    public int getUsersCount(RealmModel realm) {
        return properties.size();
    }

    @Override
    public List<UserModel> getUsers(RealmModel realm) {
        return getUsers(realm, 0, Integer.MAX_VALUE);
    }

    @Override
    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
        List<UserModel> users = new LinkedList<>();
        int i = 0;
        for (Object obj : properties.keySet()) {
            if (i++ < firstResult) continue;
            String username = (String)obj;
            UserModel user = getUserByUsername(username, realm);
            users.add(user);
            if (users.size() >= maxResults) break;
        }
        return users;
    }

getUsers() 메서드는 getUserByUsername()이 사용자를 로드하기 위해 속성 파일의 키 집합을 반복하고 getUserByUsername() 에 위임합니다. firstResultmaxResults 매개변수를 기반으로 이 호출을 인덱싱하고 있습니다. 외부 저장소에서 페이지 번호를 지원하지 않는 경우 비슷한 논리를 수행해야 합니다.

PropertyFileUserStorageProvider

    @Override
    public List<UserModel> searchForUser(String search, RealmModel realm) {
        return searchForUser(search, realm, 0, Integer.MAX_VALUE);
    }

    @Override
    public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
        List<UserModel> users = new LinkedList<>();
        int i = 0;
        for (Object obj : properties.keySet()) {
            String username = (String)obj;
            if (!username.contains(search)) continue;
            if (i++ < firstResult) continue;
            UserModel user = getUserByUsername(username, realm);
            users.add(user);
            if (users.size() >= maxResults) break;
        }
        return users;
    }

searchForUser() 의 첫 번째 선언에는 String 매개 변수가 사용됩니다. 사용자를 찾기 위해 사용자 이름 및 이메일 속성을 검색하는 데 사용하는 문자열이 되어야 합니다. 이 문자열은 하위 문자열일 수 있으므로 검색을 수행할 때 String.contains() 메서드를 사용합니다.

PropertyFileUserStorageProvider

    @Override
    public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
        return searchForUser(params, realm, 0, Integer.MAX_VALUE);
    }

    @Override
    public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
        // only support searching by username
        String usernameSearchString = params.get("username");
        if (usernameSearchString == null) return Collections.EMPTY_LIST;
        return searchForUser(usernameSearchString, realm, firstResult, maxResults);
    }

Map 매개변수를 사용하는 searchForUser() 메서드는 첫 번째, 성, 사용자 이름, 이메일에 따라 사용자를 검색할 수 있습니다. 사용자 이름만 저장하므로 사용자 이름을 기반으로만 검색합니다. 이를 위해 searchForUser() 에 위임합니다.

PropertyFileUserStorageProvider

    @Override
    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
        return Collections.EMPTY_LIST;
    }

    @Override
    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
        return Collections.EMPTY_LIST;
    }

    @Override
    public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
        return Collections.EMPTY_LIST;
    }

그룹 또는 특성을 저장하지 않으므로 다른 방법으로는 빈 목록이 반환됩니다.