IN PROGRESS
LoA (Level of Assurance) es el grado de confiabilidad de la identidad obtenida por un usuario autenticado en un sistema, es decir, el nivel de seguridad de un usuario autenticado en un sistema mediante un mecanismo de autenticación concreto.
Por ejemplo, en nuestro contexto UMU, la identidad obtenida por un usuario autenticado en CAS con su correo y contraseña tendrá el nivel LoA más bajo (LOW).
El mismo usuario, autenticado en CAS con un certificado digital, obtendrá un nivel LoA medio (SUBSTANTIAL).
Para FundeWebJS, el nivel LoA vendrá definido como un claim en el token JWT - Oauth2 devuelto por CAS.
Esta guía detalla los pasos a configurar en nuestras APIs REST FundeWebJS para securizarlas permitiendo solo el acceso a las identidades con un nivel LoA mínimo.
Esta página va dirigida a APIs FundeWebJS que necesitan configurar expresamente un nivel LoA mínimo SUBSTANTIAL o superior. Por ejemplo, algunas APIs de EADMON.
En otro caso, no es necesario realizar ninguna configuración.
Dependiendo de la situación, se distinguen dos tipos de configuraciones:
- Global: Todos los endpoints de nuestra API requerirán el mismo nivel LoA establecido en su configuración.
- Por endpoint: Configurar el nivel LoA de forma independiente para cada endpoint.
Tabla de contenidos
CONFIGURACIÓN
COMÚN
Con la configuración actual de las APIs, con el parent fundewebjs-api-parent (ver Creación y estructura de proyecto SpringBoot o Migración de APIs a Parent FundeWebJS),
no es necesario realizar ninguna configuración de librerías en pom.xml.
Clase Configuration (SecurityConfig)
Si no existe en nuestro proyecto, crear una clase SecurityConfig.java nueva a partir del código de FundeWebJSSecurityConfig.
En el application.properties (de local y de los entornos en Helm Chart), añadir la siguiente property:
//Deshabilita SecurityFilterChain por defecto fdwjs.starter.security.enable=false
GLOBAL
Configuración para establecer el mismo nivel LoA mínimo en todos los endpoints de una API:
En la clase con la configuración de seguridad (ver pasos 1 y 2 de la configuración común), añadir el siguiente Bean para configurar el filtro para LoA, estableciendo el nivel mínimo que se desea establecer en toda nuestra API:
@Bean public UmuJwtLoaAuthenticationFilter loaFilterBean() { return new UmuJwtLoaAuthenticationFilter( UmuJwtLoaLevelEnum.SUBSTANTIAL); }En la misma clase, en el método configure, añadir el filtro configurado en el paso anterior de la siguiente forma:
.addFilterAfter( loaFilterBean(), BearerTokenAuthenticationFilter.class )
Hay que añadirlo justo antes de .oauth2ResourceServer().jwt().
Por ejemplo:@Override public SecurityFilterChain filterChain( HttpSecurity http ) throws Exception { http.authorizeRequests() .mvcMatchers("/actuator/**").permitAll() .mvcMatchers("/api-docs").permitAll() .mvcMatchers(apiPath+"/public/**").permitAll() .mvcMatchers(apiPath+"/private/**").hasAnyAuthority(serverScopes) .anyRequest().authenticated().and() .addFilterAfter( loggingFilterBean(), BearerTokenAuthenticationFilter.class ) .addFilterAfter( loaFilterBean(), BearerTokenAuthenticationFilter.class ) .oauth2ResourceServer().jwt(); return http.build(); }Repetimos: ESTA CONFIGURACIÓN GLOBAL VALIDARÁ EL NIVEL DEL CLAIM LOA EN TODOS LOS ENDPOINTS DEL API.
POR MÉTODO
Configuración para establecer niveles LoA mínimos (o diferentes) en un/os endpoints concretos de nuestra API:
Clase Configuration (SecurityConfig)
En la clase con la configuración de seguridad (ver pasos 1 y 2 de la configuración común), añadir la siguiente anotación en la declaración de la clase:
@EnableGlobalMethodSecurity( prePostEnabled = true )
Añadir el siguiente Bean:
@Bean UmuJwtLoaAccessDeniedHandler accessDeniedHandler() { return new UmuJwtLoaAccessDeniedHandler(); }En la misma clase, en el método configure, añadir el accessDeniedHandler configurado en el paso anterior de la siguiente forma:
.and().exceptionHandling().accessDeniedHandler( accessDeniedHandler() )
Ejemplo de configuración completa:
public SecurityFilterChain filterChain( HttpSecurity http ) throws Exception { http .authorizeRequests() .mvcMatchers("/actuator/**").permitAll() .mvcMatchers("/api-docs").permitAll() .mvcMatchers(apiPath+"/public/**").permitAll() .mvcMatchers(apiPath+"/private/**").hasAnyAuthority(serverScopes) .anyRequest().authenticated() .and().exceptionHandling().accessDeniedHandler( accessDeniedHandler() ) .and().addFilterAfter( loggingFilterBean(), BearerTokenAuthenticationFilter.class ) .oauth2ResourceServer().jwt(); return http.build(); }
RestController
Ahora, en nuestro RestController, anotar el método con nuestro endpoint a securizar con la siguiente anotación:@PreAuthorize( "@umuJwtLoaAuthenticator.isSubstantialLoA(authentication)" )
El método isSubstantialLoA del componente umuJwtLoaAuthenticator establece el nivel mínimo de LoA para acceder al endpoint a medio (Substantial del eIDAS).
Para establecer el nivel alto (High), es necesario utilizar el método isHighLoA(authentication).
Por ejemplo:@GetMapping( "/private/afiliacion" ) @Operation( summary = "Endpoint test /afiliacion", description = "Obtiene el dto de afiliación del token llamando a serviciosgente internamente", tags = {"Serviciosgente"}, security = {@SecurityRequirement( name = "OIDC", scopes = "openid" )}, responses = { @ApiResponse( responseCode = "401", description = "Token inválido o LoA mínimo no alcanzado", content = @Content ), @ApiResponse( responseCode = "500", description = "Error de comunicación con serviciosgente", content = @Content ), @ApiResponse( responseCode = "200", description = "Datos de Afiliación del propietario del token obtenidos correctamente", content = @Content( schema = @Schema( implementation = AfiliacionDTO.class ) ) ) } ) @PreAuthorize( "@umuJwtLoaAuthenticator.isSubstantialLoA(authentication)" ) public ResponseEntity<AfiliacionDTO> getAfiliacionGente( @AuthenticationPrincipal Jwt jwt ) {Repetimos: ESTA CONFIGURACIÓN POR MÉTODO VALIDARÁ EL NIVEL DEL CLAIM LOA EN EL ENDPOINT CON LA ANOTACIÓN PREAUTHORIZE.
RESPUESTA DE ERROR
Cuando se intenta acceder a un endpoint securizado con un token sin el claim LoA mínimo (independientemente de la configuración elegida), se devolverá la siguiente información:
- Código HTTP: 401
- Cabecera en la respuesta: umu-authenticat: invalid_loa_claim
- Cabecera en la respuesta: www-authenticate: Bearer error="invalid_loa_claim",error_description="Invalid LoA Claim. Excepted level 'SUBSTANTIAL' : Found level: 'LOW' ",error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
TABLA DE EQUIVALENCIAS
Estas equivalencias son TEMPORALES y todavía pueden estar sujetas a modificaciones.
Las modificaciones serán transparentes al desarrollo. Se realizan en la librería fundewebjs-security.
| Combinación de Autenticación | LoA |
|---|---|
| LDAP (cuenta UM) | LOW |
| LDAP (cuenta UM) + MFA (OTP) | SUBSTANTIAL |
| CMN | LOW |
| CMN + MFA | SUBSTANTIAL |
| Cl@ve certificado | SUBSTANTIAL |
| Cl@ve PIN (lleva implícito el OTP) | SUBSTANTIAL |
| Cl@ve permanente | LOW |
| Cl@ve certificado + MFA | SUBSTANTIAL |
| Cl@ve PIN + MFA | SUBSTANTIAL |
| Cl@ve permanente + MFA | SUBSTANTIAL |
| *Cl@ve + DNIe | HIGH |
