Skip to content

Instantly share code, notes, and snippets.

@inspirit941
Created April 2, 2023 10:41
Show Gist options
  • Select an option

  • Save inspirit941/3aef972ac703a12655aab44779f16e45 to your computer and use it in GitHub Desktop.

Select an option

Save inspirit941/3aef972ac703a12655aab44779f16e45 to your computer and use it in GitHub Desktop.
package com.inspirit941.remoteuserstorageprovider;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.UserCredentialStore;
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 java.util.stream.Collectors;
public class RemoteUserStorageProvider implements UserStorageProvider, UserLookupProvider, CredentialInputValidator {
private KeycloakSession session;
private ComponentModel model;
private UserApiService userApiService;
public RemoteUserStorageProvider(KeycloakSession session, ComponentModel model, UserApiService userApiService) {
this.session = session;
this.model = model;
this.userApiService = userApiService;
}
// UserStorageProvider 인터페이스만으로는 validate 같은 메소드를 override 형태로 제공하는 건 아님.
// UserLookUpProvider로 getUserById / Email 등등이 가능하고
// CredentialInputValidator는 valid 검증.
// 위 세 개는 keycloak이 사용자를 검증하기 위한 최소한의 인터페이스라고 보면 된다.
@Override
public void close() {}
@Override
public UserModel getUserById(String id, RealmModel realm) { return null; }
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
// RESTEasy에서 send request to remote Web Service -> fetch userDetails.
// 셋 중 하나라도 제대로 된 UserModel을 리턴한다면 로그인 성공으로 간주함
UserModel returnValue = null;
var user = userApiService.getUserDetails(username);
if (user != null) {
returnValue = createUserModel(username, realm);
}
return returnValue;
}
private UserModel createUserModel(String username, RealmModel realm) {
return new AbstractUserAdapter(session, realm, model) {
@Override
public String getUsername() {
return username;
}
};
}
@Override
public UserModel getUserByEmail(String email, RealmModel realm) {
return null;
}
@Override
public boolean supportsCredentialType(String credentialType) {
// 예시의 경우, credential 타입인 password를 지원하는지 확인할 용도
return PasswordCredentialModel.TYPE.equals(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
// credential Type이 given user를 위한 게 맞는지 확인할 용도
// 공식 docs 내용 그대로 복사
if (!supportsCredentialType(credentialType)) return false;
var result = getCredentialStore()
.getStoredCredentialsByTypeStream(realm, user, credentialType)
.collect(Collectors.toList()).isEmpty();
return !result;
}
private UserCredentialStore getCredentialStore() {
return session.userCredentialManager();
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) {
// 사용자가 제공한 패스워드가 DB에 저장된 패스워드와 일치하는지를 확인하는 keycloak 호출 메소드.
// 이 값이 true이면 user는 authenticated된 것임.
// send a request to our remoteService Endpoint -> validate the provided password
var result = userApiService.verifyUserPassword(user.getUsername(), credentialInput.getChallengeResponse());
// getUserChallengeResponse -> 패스워드를 리턴함
if (result == null) return false;
return result.isResult();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment