7.4. 패키징 및 배포
Red Hat Single Sign-On이 공급자를 인식하려면 JAR: META-INF/services/org.keycloak.storage.UserStorageProviderFactory
에 파일을 추가해야 합니다. 이 파일에는 UserStorageProviderFactory
구현의 정규화된 클래스 이름 목록이 포함되어야 합니다.
org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
이 ScanSetting을 배포하려면 독립 실행형/deployments/
디렉토리에 복사하십시오. === Simple read-only, lookup 예제
User Storage SPI 구현의 기본 사항을 설명하기 위해 간단한 예제를 살펴보겠습니다. 이 장에서는 간단한 속성 파일에서 사용자를 조회하는 간단한 UserStorageProvider
의 구현을 확인할 수 있습니다. 속성 파일에는 사용자 이름 및 암호 정의가 포함되어 있으며 classpath의 특정 위치로 하드 코딩됩니다. 공급자는 ID와 사용자 이름으로 사용자를 검색하고 암호를 검증할 수도 있습니다. 이 공급자에서 시작된 사용자는 읽기 전용입니다.
7.4.1. 공급자 클래스
첫 번째는 UserStorageProvider
클래스입니다.
public class PropertyFileUserStorageProvider implements UserStorageProvider, UserLookupProvider, CredentialInputValidator, CredentialInputUpdater { ... }
당사의 공급자 클래스인 PropertyFileUserStorageProvider
는 많은 인터페이스를 구현합니다. 이는 SPI의 기본 요구 사항인 UserStorageProvider
를 구현합니다. 이 공급자가 저장한 사용자로 로그인할 수 있도록 하려면 UserLookupProvider
인터페이스를 구현합니다. 로그인 화면을 사용하여 에 입력한 암호를 검증할 수 있으므로 CredentialInputValidator
인터페이스를 구현합니다. 속성 파일은 읽기 전용입니다. CredentialInputUpdater
를 구현하는데, 이는 사용자가 암호를 업데이트하려고 할 때 오류 조건을 게시하려고 하기 때문입니다.
protected KeycloakSession session; protected Properties properties; protected ComponentModel model; // map of loaded users in this transaction protected Map<String, UserModel> loadedUsers = new HashMap<>(); public PropertyFileUserStorageProvider(KeycloakSession session, ComponentModel model, Properties properties) { this.session = session; this.model = model; this.properties = properties; }
이 공급자 클래스의 생성자는 KeycloakSession
,ComponentModel
및 속성 파일에 대한 참조를 저장합니다. 나중에 이 모든 것을 사용할 것입니다. 또한 로드된 사용자 맵이 있습니다. 사용자가 이 맵에 저장할 때마다 동일한 트랜잭션 내에서 다시 생성하지 않도록 합니다. 이 방법은 많은 공급업체를 따라야 하는 것이 좋습니다(즉, JPA와 통합된 모든 공급업체). 공급자 클래스 인스턴스는 트랜잭션당 한 번 생성되고 트랜잭션이 완료된 후 닫힙니다.
7.4.1.1. UserLookupProvider 구현
@Override public UserModel getUserByUsername(String username, RealmModel realm) { UserModel adapter = loadedUsers.get(username); if (adapter == null) { String password = properties.getProperty(username); if (password != null) { adapter = createAdapter(realm, username); loadedUsers.put(username, adapter); } } return adapter; } protected UserModel createAdapter(RealmModel realm, String username) { return new AbstractUserAdapter(session, realm, model) { @Override public String getUsername() { return username; } }; } @Override public UserModel getUserById(String id, RealmModel realm) { StorageId storageId = new StorageId(id); String username = storageId.getExternalId(); return getUserByUsername(username, realm); } @Override public UserModel getUserByEmail(String email, RealmModel realm) { return null; }
getUserByUsername()
메서드는 사용자가 로그인할 때 Red Hat Single Sign-On 로그인 페이지에서 호출합니다. 구현에서 먼저 로드된Users
맵을 확인하여 사용자가 이 트랜잭션 내에 이미 로드되었는지 확인합니다. 로드되지 않은 경우 사용자 이름의 속성 파일을 찾습니다. UserModel
의 구현을 생성하고 나중에 참조할 수 있도록 Load Users
에 저장한 후 이 인스턴스를 반환합니다.
createAdapter()
메서드는 helper 클래스 org.keycloak.storage.adapter.AbstractUserAdapter
를 사용합니다. UserModel
에 대한 기본 구현을 제공합니다. 사용자 이름을 외부 ID로 사용하여 필요한 스토리지 ID 형식을 기반으로 사용자 ID를 자동으로 생성합니다.
"f:" + component id + ":" + username
AbstractUserAdapter
의 모든 get 메서드는 null 또는 빈 컬렉션을 반환합니다. 그러나 역할 및 그룹 매핑을 반환하는 방법은 모든 사용자의 영역에 대해 구성된 기본 역할 및 그룹을 반환합니다. AbstractUserAdapter
의 모든 세트 메서드에서 org.keycloak.storage.ReadOnlyException
이 발생합니다. 따라서 관리 콘솔에서 사용자를 수정하려고 하면 오류가 발생합니다.
getUserById()
메서드는 org.keycloak.storage.StorageId
도우미 클래스를 사용하여 id
매개변수를 구문 분석합니다. StorageId.getExternalId()
메서드를 호출하여 id
매개변수에 포함된 사용자 이름을 가져옵니다. 그러면 메서드가 getUserByUsername()
에 위임됩니다.
이메일은 저장되지 않으므로 getUserByEmail()
메서드는 null을 반환합니다.
7.4.1.2. CredentialInputValidator 구현
다음으로 CredentialInputValidator
에 대한 메서드 구현을 살펴보겠습니다.
@Override public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { String password = properties.getProperty(user.getUsername()); return credentialType.equals(PasswordCredentialModel.TYPE) && password != null; } @Override public boolean supportsCredentialType(String credentialType) { return credentialType.equals(PasswordCredentialModel.TYPE); } @Override public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { if (!supportsCredentialType(input.getType())) return false; String password = properties.getProperty(user.getUsername()); if (password == null) return false; return password.equals(input.getChallengeResponse()); }
isConfiguredFor()
메서드를 런타임에서 호출하여 사용자에게 특정 인증 정보 유형이 구성되었는지 확인합니다. 이 방법은 암호가 사용자에 대해 설정되어 있는지 확인합니다.
supportsCredentialType()
메서드는 특정 인증 정보 유형에 대해 검증이 지원되는지 여부를 반환합니다. 인증 정보 유형이 password
인지 확인합니다.
isValid()
메서드는 암호의 유효성을 검사합니다. CredentialInput
매개변수는 실제로 모든 인증 정보 유형에 대한 추상 인터페이스일 뿐입니다. 인증 정보 유형을 지원하고 UserCredentialModel
의 인스턴스이기도 합니다. 사용자가 로그인 페이지에서 로그인하면 암호 입력의 일반 텍스트가 UserCredentialModel
의 인스턴스에 배치됩니다. isValid()
메서드는 속성 파일에 저장된 일반 텍스트 암호에 대해 이 값을 확인합니다. 반환 값이 true
이면 암호가 유효함을 의미합니다.
7.4.1.3. CredentialInputUpdater 구현
앞에서 언급했듯이 CredentialInputUpdater
인터페이스를 구현하는 유일한 이유는 사용자 암호를 수정하지 않는 것입니다. 그렇지 않으면 런타임에서 Red Hat Single Sign-On 로컬 스토리지에서 암호를 덮어쓸 수 있기 때문입니다. 이 장의 뒷부분에서 이에 대해 자세히 살펴보겠습니다.
@Override public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { if (input.getType().equals(PasswordCredentialModel.TYPE)) throw new ReadOnlyException("user is read only for this update"); return false; } @Override public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { } @Override public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { return Collections.EMPTY_SET; }
updateCredential()
메서드는 인증 정보 유형이 password인지 확인하기만 합니다. 이 경우 ReadOnlyException
이 throw됩니다.
7.4.2. 공급자 팩토리 구현
이제 공급자 클래스가 완료되었으므로 이제 공급자 팩토리 클래스에 주의를 기울입니다.
public class PropertyFileUserStorageProviderFactory implements UserStorageProviderFactory<PropertyFileUserStorageProvider> { public static final String PROVIDER_NAME = "readonly-property-file"; @Override public String getId() { return PROVIDER_NAME; }
먼저 알아두어야 할 것은 UserStorageProviderFactory
클래스를 구현할 때 결정적 공급자 클래스 구현을 템플릿 매개변수로 전달해야 한다는 것입니다. 여기에서 이전에 정의한 공급자 클래스를 지정합니다. PropertyFileUserStorageProvider
.
template 매개변수를 지정하지 않으면 공급자가 작동하지 않습니다. 런타임은 클래스 인트로스펙션을 수행하여 공급자가 구현하는 기능 인터페이스를 결정합니다.
getId()
메서드는 런타임에서 팩토리를 식별하고 영역의 사용자 스토리지 공급자를 활성화하려는 경우 admin 콘솔에 표시된 문자열이기도 합니다.
7.4.2.1. 초기화
private static final Logger logger = Logger.getLogger(PropertyFileUserStorageProviderFactory.class); protected Properties properties = new Properties(); @Override public void init(Config.Scope config) { InputStream is = getClass().getClassLoader().getResourceAsStream("/users.properties"); if (is == null) { logger.warn("Could not find users.properties in classpath"); } else { try { properties.load(is); } catch (IOException ex) { logger.error("Failed to load users.properties file", ex); } } } @Override public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) { return new PropertyFileUserStorageProvider(session, model, properties); }
UserStorageProviderFactory
인터페이스에는 구현할 수 있는 선택적 init()
메서드가 있습니다. Red Hat Single Sign-On이 부팅되면 각 공급자 팩토리의 인스턴스 한 개만 생성됩니다. 또한 부팅 시 init()
메서드가 이러한 팩토리 인스턴스마다 호출됩니다. postInit()
도 구현할 수 있습니다. 각 팩토리의 init()
메서드가 호출되면 해당 postInit()
메서드가 호출됩니다.
init()
메서드 구현에서는 classpath에서 사용자 선언이 포함된 속성 파일을 찾습니다. 그런 다음 저장된 사용자 이름 및 암호 조합으로 properties
필드를 로드합니다.
Config.Scope
매개변수는 서버 구성을 통해 구성된 팩토리 구성입니다.
예를 들어 standalone.xml
에 다음을 추가하여 다음을 수행합니다.
<spi name="storage"> <provider name="readonly-property-file" enabled="true"> <properties> <property name="path" value="/other-users.properties"/> </properties> </provider> </spi>
사용자 속성 파일의 클래스 경로를 하드 코딩하지 않고 지정할 수 있습니다. 그런 다음 PropertyFileUserStorageProviderFactory.init()
에서 구성을 검색할 수 있습니다.
public void init(Config.Scope config) { String path = config.get("path"); InputStream is = getClass().getClassLoader().getResourceAsStream(path); ... }
7.4.2.2. 메서드 생성
공급자 팩토리를 생성하는 마지막 단계는 create()
메서드입니다.
@Override public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) { return new PropertyFileUserStorageProvider(session, model, properties); }
PropertyFileUserStorageProvider
클래스를 할당하기만 하면 됩니다. 이 생성 방법은 트랜잭션당 한 번 호출됩니다.
7.4.3. 패키징 및 배포
공급자 구현을 위한 클래스 파일은 ScanSetting에 배치해야 합니다. 또한 META-INF/services/org.keycloak.storage.UserStorageProviderFactory
파일에서 공급자 팩토리 클래스를 선언해야 합니다.
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
이 ScanSetting을 배포하려면 standalone/deployments/
디렉터리에 복사합니다.
7.4.4. 관리 콘솔에서 공급자 활성화
관리 콘솔의 사용자 페더레이션 페이지에서 영역당 사용자 스토리지 공급자를 활성화합니다.
절차
목록에서 방금 만든 공급자를 선택합니다.
readonly-property-file
.해당 공급자의 구성 페이지가 표시됩니다.
- 구성할 항목이 없으므로 저장 을 클릭합니다.
기본 사용자 페더레이션 페이지로 돌아가기
이제 공급자가 표시됩니다.
이제 users.properties
파일에 선언된 사용자로 로그인할 수 있습니다. 이 사용자는 로그인 후 계정 페이지를 볼 수 있습니다.