Mostrando entradas con la etiqueta Identity Server. Mostrar todas las entradas
Mostrando entradas con la etiqueta Identity Server. Mostrar todas las entradas

lunes, 6 de junio de 2022

WSO2 Identity Server: Como hacer un custom user store

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'. 


Este sería el funcionamiento para el IS 5.10. Y en la siguiente versión, aunque los elementos explicados anteriormente se mantienen iguales, la configuración es distinta. 

Para ello, deberemos configurar todos los User Stores disponibles en el IS, los de por defecto y los que creemos nosotros, en el fichero de configuración deployment.toml. 

[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í. 

sábado, 19 de octubre de 2019

WSO2 Identity Server: Como leer atributos de user stores secundarios

Buenas, hoy traemos algo sencillito pero que no se encuentra fácilmente en la documentación. Y es como mostrar los atributos del usuario que no sean del user store principal. Es decir, que cuando accedamos al perfil de un usuario de un user store secundario, en su nombre, apellidos, etc veamos los valores almacenados en dicho user store en vez de verlos en blancos. 

Pero antes vamos a aclarar varios conceptos:
  • WSO2 Identity Server: Es un servidor basado en la gestión de identidades y accesos, con el cual podremos gestionar usuarios de distintos orígenes, single sign on, control de accesos, etc. 
  • User Store: Es básicamente un almacén de usuarios, pudiendo tener su origen en una base de datos, un LDAP o un Active Directory. Eso sí, necesitaremos una correcta configuración para su correcto funcionamiento. 
  • Claims: Lo que podemos entender como un atributo asociado a un usuario. A través de estas podremos identificar a un usuario. Y están organizadas en base esquemas, independientemente de su origen. 
Pues sabiendo todo eso, los pasos son los siguientes:
  • Añadir un user store

  • Modificamos una de las claims predefinidas del usuario. Para ello vamos a Claims > List > http://wso2.org/claims. Editamos la claim que queramos asociar. 
  • A la hora de editarlo seleccionaremos el user store secundario recién creado y en el campo asociado, indicaremos el campo que contiene el atributo deseado.

Si queremos que este atributo se vea al entrar por defecto en la ventana de perfil del usuario deberemos marcar la opción Support by default.