...
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 endpointmétodo: Configurar el nivel LoA de forma independiente para cada endpoint.
Tabla de contenidos
| Tabla de contenidos | ||
|---|---|---|
|
CONFIGURACIÓN
COMÚN
| Info | ||
|---|---|---|
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. | ||
| Expandir | ||
|
| Bloque de código | ||
|---|---|---|
| ||
<dependency>
<groupId>es.um.atica.fundewebjs.fundewebjs-api</groupId>
<artifactId>fundewebjs-security</artifactId>
<version>${fdwjs.version}</version>
</dependency> |
| Info |
|---|
La librería se encuentra a partir de la versión fdwjs 1.0.7-SNAPSHOT. |
Clase Configuration (SecurityConfig)
Clase Configuration (SecurityConfig)
Si no existe en nuestro proyecto, crear una clase SecurityConfig.java nueva a partir del código de FundeWebJSSecurityConfig.
Si no existe en nuestro proyecto, crear una clase SecurityConfig.java nueva a partir del código de FundeWebJSSecurityConfig (ubicada en la librería fundewebjs-security).Bloque de código language java title FundeWebJSSecurityConfig collapse true @Log4j2 @Configuration public class FundeWebJSSecurityConfig { @Value( "${server.scopes}" ) private String[] serverScopes; @Value( "${app.server.path}" ) private String apiPath; /** * Proveedor de gestion de acceoss */ @Bean( name = "fundeWebJsDefaultSecurityFilterChain" ) public SecurityFilterChain filterChain( HttpSecurity http ) throws Exception { log.debug( "Carga SecurityFilterChain desde FundewebJs-Starter" ); http.requestMatchers().antMatchers( "/public/**" ).and().requestMatchers().antMatchers( apiPath + "/**" ).and() .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS ) // configuro politica de sesion sin estado .and().cors() // Aniado configuracion CORS por defecto .and().csrf().disable().authorizeRequests().mvcMatchers( apiPath + "/public/**" ).permitAll() .mvcMatchers( apiPath + "/**" ).hasAnyAuthority( serverScopes ).anyRequest().authenticated().and() .addFilterAfter( new FundeWebJSLoggingAuthorizationFilter(), BearerTokenAuthenticationFilter.class ) .oauth2ResourceServer() .jwt(); return http.build(); } }En el application.properties (de local y de los entornos en Helm Chart), añadir la siguiente property:
...
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:
Bloque de código @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:
Bloque de código language java .addFilterAfter( loaFilterBean(), BearerTokenAuthenticationFilter.class )
Hay que añadirlo justo antes de .oauth2ResourceServer().jwt().
Por ejemplo:Bloque de código language java @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(); }Advertencia 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:
Bloque de código language java @EnableGlobalMethodSecurity( prePostEnabled = true )
Añadir el siguiente Bean:
Bloque de código language java @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:
Bloque de código language java .and().exceptionHandling().accessDeniedHandler( accessDeniedHandler() )
Ejemplo de configuración completa:
Bloque de código language java 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:Bloque de código language java @PreAuthorize( "@umuJwtLoaAuthenticator.isSubstantialLoA(authentication)" )
Advertenciainfo 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:
Bloque de código language java @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 = "Token subject.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 ) {Advertencia 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"
...
| 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 | LOWSUBSTANTIAL |
| Cl@ve certificado + MFA | SUBSTANTIAL |
| Cl@ve PIN + MFA | SUBSTANTIAL |
| Cl@ve permanente + MFA | SUBSTANTIAL |
| *Cl@ve + DNIe | HIGH |
...