Versiones comparadas

Clave

  • Se ha añadido esta línea.
  • Se ha eliminado esta línea.
  • El formato se ha cambiado.

...

Bloque de código
languagejava
themeEclipse
titleEjemplo 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_miotrorecursomiotroscope" )
	.mvcMatchers( apiPath + "/**" ).hasAnyAuthority( serverScopes ).anyRequest().authenticated()
	.and().addFilterAfter( loggingFilterBean(), BearerTokenAuthenticationFilter.class )
	.oauth2ResourceServer().jwt();
}

...

Cuando llega una petición al endpoint securizado de esta forma con un token que no incluye el scope configuradoa 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: 

...

Bloque de código
languagejava
themeEclipse
...
.mvcMatchers( apiPath + "**/mirecurso/misubrecursoconotroscope/**" ).hasAuthority( "SCOPE_miotrorecursomiotroscope" )
.mvcMatchers( apiPath + "/**" ).hasAnyAuthority( serverScopes ).anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler( new MiOtroAccessDeniedHandler())
.and().addFilterAfter( loggingFilterBean(), BearerTokenAuthenticationFilter.class )
.oauth2ResourceServer().jwt()
...

...

La segunda forma para configurar endpoints para ser 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.

Por ejemplo, podríamos añadir a la respuesta un body y una cabecera propia con este ExceptionHandler: 

Bloque de código
languagejava
themeEclipse
titleExceptionHandler 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".

...