En este post que es y para qué sirve un Custom User Store en el WSO2 Identity Server. Y además también veremos pequeñas diferencias de desplegarlo en una versión en el Identity Server 5.10, 5.11 o 5.12.
El IS es un producto que nos permite entre otras funciones gestionar los usuarios de distintos orígenes de forma centralizada. A esta fuente donde se almacenan los usuarios y sus roles, es un User Store. Por defecto, él IS nos permite configurar distintos User Stores en base a su origen (LDAP, BBDD, etc). Pero nosotros, también podemos crear nuestro propio tipo de User Store basando en uno de estos y personalizando determinados métodos.
Como siempre, vamos a crear un ejemplo sencillo y paso a paso. En él, personalizaremos el User Store, para que verifique que la contraseña sea el texto resultante de la codificación del nombre del usuario. Y el algoritmo de codificación lo podremos configurar en el propio custom user store.
Empezamos creando una clase que herede de alguno de los User Store por defecto. A partir de la versión 5.11 solo existen los User Store con prefijo UniqueID, que aseguran que cada usuario tenga un identificador único.
public class CustomUserStore10 extends UniqueIDJDBCUserStoreManager { private static final Log LOG = LogFactory.getLog(CustomUserStore10.class); private final static String CUS_ALG_NAME = "algorithm"; private final static String CUS_ALG_DFLT_VAL = "BASE64"; private final static String CUS_ALG_DIS_NAME = "Algorithm"; private final static String CUS_ALG_DESC = "Algorithm for encrypt username"; private final static String CUS_ALG_PROP_DESC = CUS_ALG_DIS_NAME + "#" + CUS_ALG_DESC; public CustomUserStore10(final RealmConfiguration realmConfig, final Map<String, Object> properties, final ClaimManager claimManager, final ProfileConfigurationManager profileManager, final UserRealm realm, final Integer tenantId) throws UserStoreException { super(realmConfig, properties, claimManager, profileManager, realm, tenantId); this.realmConfig = realmConfig; } @Override protected AuthenticationResult doAuthenticateWithUserName(final String userName, final Object credential) throws UserStoreException {
String algorithm = realmConfig.getUserStoreProperty(CUS_ALG_NAME); String sCredential = (String) credential; String myPwdEncripted = null; if ("BASE64".equals(algorithm)) { myPwdEncripted = Base64.getEncoder().encodeToString(userName.getBytes()); } if (sCredential.equalsIgnoreCase(myPwdEncripted)) { User user = getUser(getUserIDFromUserName(userName), userName); authResult = new AuthenticationResult(AuthenticationResult.AuthenticationStatus.SUCCESS); authResult.setAuthenticatedUser(user); } else { authResult = new AuthenticationResult(AuthenticationResult.AuthenticationStatus.FAIL); authResult.setFailureReason(new FailureReason("Password change required.")); } return authResult; } @Override public Properties getDefaultUserStoreProperties() { Properties properties = super.getDefaultUserStoreProperties(); List<Property> mandatories = Stream.of(properties.getMandatoryProperties()).collect(Collectors.toList()); // create new configuration property Property apiURL = new Property(CUS_ALG_NAME, CUS_ALG_DFLT_VAL, CUS_ALG_PROP_DESC, null); // add to mandatory properties mandatories.add(apiURL); properties.setMandatoryProperties(mandatories.stream().toArray(Property[]::new)); return properties; } }
Para el ejemplo hemos extendido de la clase UniqueIDJDBCUserStoreManager, y para ello hemos creado un esquema de BBDD con el formato necesario para ser utilizado por WSO2. Este scripts se encuentra dentro de la propia imagen del producto y contiene de distintos tipos, en función de la BBDD donde queramos crearlo.
En el método getDefaultUserStoreProperties podemos añadir nuevos parámetros de configuración, los cuales se mostrarán en la UI de creación del User Store. Y el método doAuthenticateWithUserName será el que sobrescribiremos para modificar la lógica por defecto cuando se logea un usuario.
Si queremos hacer tareas más complejas y no vemos correctamente cuál es el método a sobrescribir, siempre podemos bajar el nivel de debug del paquete donde se encuentre la clase que extendemos y verificar cuales son los métodos que se invocan.
El siguiente paso será crear una clase que permita cargar nuestra clase de forma dinámica con OSGI. Para ello la clase debe tener los siguientes elementos:
- Anotación @Component asociada a la clase. Permitirá crear la configuración de forma automática, a través del maven-bundle-plugin, que debe ser incluida en nuestro JAR.
- Anotación @Activate asociada a un método. El cual registrará nuestra clase dentro del contexto de WSO2.
@Component(name = "custom.user.store.manager.dscomponent", service = CustomUserStore10DSC.class, immediate = true) public class CustomUserStore10DSC { @Activate protected void activate(final ComponentContext ctxt) { UserStoreManager customUSManager = new CustomUserStore10(); ctxt.getBundleContext().registerService(UserStoreManager.class.getName(), customUSManager, null); } }
Ahora toca definir el fichero pom.xml dónde principalmente configuraremos el plugin maven-bundle-plugin.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>es.home.example.wso2is</groupId> <artifactId>custom-user-store-10</artifactId> <version>1.0.0</version> <packaging>bundle</packaging> <dependencies> <dependency> <groupId>org.wso2.carbon</groupId> <artifactId>org.wso2.carbon.user.core</artifactId> <version>4.6.0</version>
<scope>provided</scope></dependency> <dependency> <groupId>org.wso2.carbon</groupId> <artifactId>org.wso2.carbon.utils</artifactId> <version>4.6.0</version>
<scope>provided</scope></dependency> <dependency> <groupId>org.wso2.carbon</groupId> <artifactId>org.wso2.carbon.user.api</artifactId> <version>4.6.0</version>
<scope>provided</scope></dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.apache.felix.scr.ds-annotations</artifactId> <scope>provided</scope> <version>1.2.10</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>3.2.0</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName> <Bundle-Name>${project.artifactId}</Bundle-Name> <Private-Package>es.home.example.wso2is.userstore.internal</Private-Package> <Export-Package>!es.home.example.wso2is.userstore.internal, es.home.example.wso2is.userstore.*</Export-Package> <_dsannotations>*</_dsannotations> <Import-Package>org.osgi.service.component.*;version="${osgi.service.component.imp.pkg.version.range}", org.wso2.carbon.user.api; version="${carbon.user.api.imp.pkg.version.range}", org.wso2.carbon.user.core.*; version="${carbon.kernel.package.import.version.range}", org.wso2.carbon.utils; version="${carbon.kernel.package.import.version.range}", org.apache.commons.logging; version="${import.package.version.commons.logging}", org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}", javax.servlet; version=2.4.0, javax.servlet.http; version=2.4.0, *;resolution:=optional</Import-Package> </instructions> </configuration> </plugin> </plugins> </build> <properties> <carbon.user.api.imp.pkg.version.range>[1.0, 2.0.0)</carbon.user.api.imp.pkg.version.range> <carbon.kernel.package.import.version.range>[4.5.0, 5.0.0)</carbon.kernel.package.import.version.range> <import.package.version.commons.logging>[1.2.0,2.0.0)</import.package.version.commons.logging> <osgi.framework.imp.pkg.version.range>[1.7.0, 2.0.0)</osgi.framework.imp.pkg.version.range> <osgi.service.component.imp.pkg.version.range>[1.2.0, 2.0.0)</osgi.service.component.imp.pkg.version.range> </properties></project>
Por último, solo quedaría desplegarlo en la carpeta ${WSO2_HOME}/repository/components/dropins/ y arrancar el WSO2 IS. En la carpeta src/test/resources hay un docker-compose del código fuente, que os puede ayudar a desplegarlo. Una vez arrancado, crearemos el User Store desde la opción del menú lateral 'add User Store'.
[user_store_mgt]
allowed_user_stores=["org.wso2.carbon.user.core.jdbc.UniqueIDJDBCUserStoreManager",
"org.wso2.carbon.user.core.ldap.UniqueIDActiveDirectoryUserStoreManager",
"org.wso2.carbon.user.core.ldap.UniqueIDReadOnlyLDAPUserStoreManager",
"org.wso2.carbon.user.core.ldap.UniqueIDReadWriteLDAPUserStoreManager",
"es.home.example.wso2is.userstore.CustomUserStore10"]
Y esto ha sido todos, si quieres ver todo el código puedes hacerlo aquí.
No hay comentarios:
Publicar un comentario