...
Esta página trata sobre la configuración de nuestra API FundeWebJS para validar tokens con scopes diferentes a los recibidos desde micampus (scope: micampus), las respuestas que produce esa configuración en caso de recibir un token inválido y cómo personalizar esa respuesta.
| Tabla de contenidos |
|---|
Guía detallada
1. Configuración global de otros scopes en la aplicación
...
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
#Valida los scopes 'micampus' y 'miotroscope'
server.scopes=SCOPE_micampus,SCOPE_miotroscope |
2. Configurar endpoints por scope
...
La librería fundewebjs-security contiene un fichero FundeWebJSSecurityConfig por defecto. Para utilizar otra configuración hay que realizar los siguientes pasos:
- 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-spring-boot-starter). Del código de esta clase, quitaremos esta línea:
Bloque de código @ConditionalOnExpression( "${fdwjs.starter.cors.enable:true}" ) En el application.properties (de local y de los entornos en Helm Chart), añadir la siguiente property:
Bloque de código //Deshabilita SecurityFilterChain por defecto fdwjs.starter.security.enable=false
A continuación se detallan las configuraciones específicas para autorizar por scopes:
1. Configuración global de otros scopes en la aplicación
La primera forma para validar scopes diferentes en FundeWebJS es añadirlos a la property server.scopes del application.properties. Esta property ya se encuentra configurada por defecto para el scope "micampus".
Para añadir un scope nuevo, hay que ponerlo a continuación del existente separado por comas y con el prefijo "SCOPE_".
Por ejemplo:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
#Valida los scopes 'micampus' y 'miotroscope'
server.scopes=SCOPE_micampus,SCOPE_miotroscope |
2. Configurar endpoints por scope
Con la configuración del punto 1 se configura TODA la aplicación para permitir tokens que contengan cualquiera de los scopes configurados.
Esto significa que un token obtenido desde micampus por cualquier usuario sería válido para acceder a cualquier endpoint.
Si hemos tenido que configurar varios scopes, lo normal es que está situación sea no deseada (no queremos que cualquier token sirva para acceder a endpoints que requieran un nivel diferente de acceso).
En este apartado se explica cómo configurar nuestra aplicación para securizar diferentes endpoints con scopes distintos.
Existen dos alternativas:
- Configurar los mvcMatchers en el SecurityConfig.
- Utilizar la anotación PreAuthorize en cada método Java de nuestro RestController.
2.1 Configuración de MvcMatchers
La primera forma de configuración es añadir un mvcMatcher en el método filterChain de nuestra clase "SecurityConfig".
Se añade el mvcMatcher que capture la/s rutas/s a securizar con nuestro nuevo scope (antes del mvcMatcher configurado por defecto para private-apiPath) y se le añade .hasAuthority("SCOPE_miotroscope").
Por ejemplo:
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
public SecurityFilterChain filterChain( |
- Configurar los mvcMatchers en el SecurityConfig.
- Utilizar la anotación PreAuthorize en cada método Java de nuestro RestController.
Configuración de MvcMatchers
La primera forma de configuración es añadir un mvcMatcher en el método configure de nuestra clase "SecurityConfig".
Se añade el mvcMatcher que capture la/s rutas/s a securizar con nuestro nuevo scope (antes del mvcMatcher configurado por defecto para private-apiPath) y se le añade .hasAuthority("SCOPE_miotroscope").
Ejemplo:
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
@Override protected void configure( HttpSecurity http ) throws Exception { http http.requestMatchers().antMatchers( "/public/**" ).and().requestMatchers().antMatchers( apiPath + "/**" ).and() .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS ) .and().cors() .and().csrf().disable().authorizeRequests() .mvcMatchers( "/public/**" ).permitAll() .mvcMatchers( apiPath + "**/mirecurso/misubrecursoconotroscope/**" ).hasAuthority( "SCOPE_miotroscope" ) .mvcMatchers( apiPath + "/**" ).hasAnyAuthority( serverScopes ).anyRequest().authenticated() .and().addFilterAfter( loggingFilterBean(), BearerTokenAuthenticationFilter.class ) .oauth2ResourceServer().jwt(); return http.build(); } |
-- Respuesta de error --
Cuando llega una petición a el/los endpoints que concuerdan con la ruta del mvcMatcher y un scope inválido , la librería spring-security-oauth2-resource-server toma el control y devuelve la respuesta de la siguiente forma:
- Status Code: 403 Forbidden
- Body: VACIO
- Cabecera WWW-Authenticate: Bearer error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token.", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
| Advertencia |
|---|
¡¡OJO!!OJO¡¡ Como Como la librería spring-security-oauth2-resource-server toma ha tomado el control del error, cualquier otro manejo de la excepción configurado según según Manejo de Errores en FundeWebJS NO TENDRÁ EFECTO. |
...
| Info |
|---|
Tomar de ejemplo la clase BearerTokenAccessDeniedHandler (handler por defecto de spring-security-oauth2-resource-server) para crear nuestra clase AccessDeniedExceptionHandler propia. |
2.2 Configuración con PreAuthorize
La segunda forma para configurar endpoints y securizarlos por scope es utilizando la anotación de spring-security @PreAuthorize.
...
La diferencia con MvcMatchers es que con esta configuración la validación se realiza al final, justo antes de entrar al método, y que la aplicación puede tomar el control del manejo de la excepción usando alguno de los mecanismos de Manejo de Errores en FundeWebJS para capturar la excepción AccessDeniedException.
Por ejemplo, podríamos añadir a la respuesta un body y una cabecera propia con este ExceptionHandler:
...
| Bloque de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
3.1. Error Codes
When a request fails, the resource server responds using the
appropriate HTTP status code (typically, 400, 401, 403, or 405) and
includes one of the following error codes in the response:
invalid_request
The request is missing a required parameter, includes an
unsupported parameter or parameter value, repeats the same
parameter, uses more than one method for including an access
token, or is otherwise malformed. The resource server SHOULD
respond with the HTTP 400 (Bad Request) status code.
invalid_token
The access token provided is expired, revoked, malformed, or
invalid for other reasons. The resource SHOULD respond with
the HTTP 401 (Unauthorized) status code. The client MAY
request a new access token and retry the protected resource
request.
insufficient_scope
The request requires higher privileges than provided by the
access token. The resource server SHOULD respond with the HTTP
403 (Forbidden) status code and MAY include the "scope"
attribute with the scope necessary to access the protected
resource.
If the request lacks any authentication information (e.g., the client
was unaware that authentication is necessary or attempted using an
unsupported authentication method), the resource server SHOULD NOT
include an error code or other error information.
For example:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example" include the "scope"
attribute with the scope necessary to access the protected
resource.
If the request lacks any authentication information (e.g., the client
was unaware that authentication is necessary or attempted using an
unsupported authentication method), the resource server SHOULD NOT
include an error code or other error information.
For example:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example" |
3. Documentar con Springdoc-OPENAPI
Utilizando @PreAuthorize es posible configurar la generación de la documentación para que aparezca el Scope requerido.
Para hacerlo solo sería necesario añadir una clase de configuración como ésta:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
@Configuration
public class OpenApiConfiguration {
@Bean
public OperationCustomizer operationCustomizer() {
return ( operation, handlerMethod ) -> {
Optional<PreAuthorize> preAuthorizeAnnotation = Optional
.ofNullable( handlerMethod.getMethodAnnotation( PreAuthorize.class ) );
StringBuilder sb = new StringBuilder();
if ( preAuthorizeAnnotation.isPresent() ) {
Pattern patternAuthority = Pattern.compile( "'SCOPE_\\w+'" );
Matcher matcher = patternAuthority.matcher( ( preAuthorizeAnnotation.get() ).value() );
if ( matcher.find() ) {
sb.append( "Este endpoint requiere: **SCOPE " ).append( matcher.group().replaceAll( "SCOPE_", "" ) )
.append( "**<br />" );
}
sb.append( "<br />" );
}
sb.append( operation.getDescription() );
operation.setDescription( sb.toString() );
return operation;
};
}
} |
El resultado de esta configuración se puede ver en la siguiente imagen:
Referencias
https://www.baeldung.com/spring-security-method-security
Artículos Relacionados
| Contenido por etiqueta | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
...
