Estás viendo una versión antigua de esta página. Ve a la versión actual.

Comparar con el actual Ver el historial de la página

« Anterior Versión 20 Siguiente »

La configuración de seguridad estándar de FundeWebJS permite solamente validar los tokens JWT de OAUTH obtenidos desde micampus.

En ocasiones una API necesitará validar otro tipo de tokens OAUTH obtenidos desde otras aplicaciones, scopes u otros flujos OAUTH.
Por ejemplo: validación de tokens OAUTH del flujo CLIENT CREDENTIALS para la comunicación entre APIs FundeWeb y FundeWebJS.

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.

Guía detallada


1. Configuración global de otros scopes en la aplicación

El primer paso 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

application.properties
#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 paso 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.

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").

Por ejemplo:

Ejemplo mvcMatcher
@Override
protected void configure( HttpSecurity http ) throws Exception {
	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();
}


-- 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"


¡¡OJO!! Como la librería spring-security-oauth2-resource-server ha tomado el control, cualquier otro manejo de la excepción configurado según Manejo de Errores en FundeWebJS NO TENDRÁ EFECTO.


Para sobreescribir el comportamiento de la respuesta de error habrá que crear una clase AccessDeniedExceptionHandler propia y configurarla en el mismo método configure de nuesta clase "SecurityConfig" añadiendo el exceptionHandling de esta forma: 

...
.mvcMatchers( apiPath + "**/mirecurso/misubrecursoconotroscope/**" ).hasAuthority( "SCOPE_miotroscope" )
.mvcMatchers( apiPath + "/**" ).hasAnyAuthority( serverScopes ).anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler( new MiOtroAccessDeniedHandler())
.and().addFilterAfter( loggingFilterBean(), BearerTokenAuthenticationFilter.class )
.oauth2ResourceServer().jwt()
...

Tomar de ejemplo la clase BearerTokenAccessDeniedHandler (handler por defecto de spring-security-oauth2-resource-server) para crear nuestra clase AccessDeniedExceptionHandler propia.


Configuración con PreAuthorize


La segunda forma para configurar endpoints y securizarlos por scope es utilizando la anotación de spring-security @PreAuthorize.

Para hacerlo hay que:

  • Anotar la clase SecurityConfig con @EnableGlobalMethodSecurity( prePostEnabled = true )
  • Anotar el método java que publica nuestro endpoint (o directamente todo el RestController en caso de hacer una clase específica para ese scope) con @PreAuthorize( "hasAuthority('SCOPE_miotroscope')" )

-- Respuesta de error --

Cuando llega una petición a el/los endpoints  con la anotación PreAuthroize y un token inválido la respuesta por defecto será del mismo tipo que en el caso anterior:

  • 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"


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: 

ExceptionHandler para AccessDeniedException
	@ExceptionHandler( AccessDeniedException.class )
	public ResponseEntity<ResponseException> accessDeniedException( HttpServletRequest request,
		AccessDeniedException e ) throws IOException {
        
        ....
		MiDTOdeError responseErrorBody = new MiDTOdeError([CON LOS ATRIBUTOS QUE QUIERA SACAR EN EL BODY]) //Puede usarse la clase BearerTokenError y BearerTokenErrors para generarlo.

		Map<String, String> parameters = new LinkedHashMap<>();
		if ( request.getUserPrincipal() instanceof AbstractOAuth2TokenAuthenticationToken ) {
			parameters.put( "error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE );
			parameters.put( "error_description",
					"The request requires higher privileges than provided by the access token." );
			parameters.put( "error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1" );
		}
		String wwwAuthenticate = computeWWWAuthenticateHeaderValue( parameters );

		HttpHeaders headers = new HttpHeaders();
		headers.add( HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate );
		headers.add( "UMU-Authenticate", parameters.get( "error" ) );

		return new ResponseEntity<>( responseErrorBody, headers, HttpStatus.FORBIDDEN );

	}

	private static String computeWWWAuthenticateHeaderValue( Map<String, String> parameters ) {
		StringBuilder wwwAuthenticate = new StringBuilder();
		wwwAuthenticate.append( "Bearer" );
		if ( !parameters.isEmpty() ) {
			wwwAuthenticate.append( " " );
			int i = 0;
			for ( Map.Entry<String, String> entry : parameters.entrySet() ) {
				wwwAuthenticate.append( entry.getKey() ).append( "=\"" ).append( entry.getValue() ).append( "\"" );
				if ( i != ( parameters.size() - 1 ) ) {
					wwwAuthenticate.append( ", " );
				}
				i++;
			}
		}
		return wwwAuthenticate.toString();
	}


3. Buenas prácticas

Como se ha mencionado en los apartados de respuesta de error, los mecanismos estándar de manejo de excepciones de spring-security y spring-security-oauth2-resource-server devuelven siempre un 403 Forbidden, con body vacío y con la cabecera WWW-Authenticate informando del error "insufficient_scope".

Esta configuración se puede sobreescribir pero, siguiendo el RFC de Oauth se recomienda mantener el 403 Forbidden y la cabecera con "insufficient_scope" => "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".

RFC Oauth - Error Codes
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"


Referencias

https://www.baeldung.com/spring-security-method-security

Artículos Relacionados

No hay ningún contenido con las etiquetas especificadas


  • Sin etiquetas