...
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
@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 | ||||
|---|---|---|---|---|
| ||||
... .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 | ||||||
|---|---|---|---|---|---|---|
| ||||||
@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".
...