Personalizar HTTP 403 Acceso denegado

Hasta el momento ya hemos visto varios tutoriales del framework de seguridad Spring Security, seguro habrás notado que, si inicias sesión con un usuario he intentas acceder a una página a la cual el mismo no tiene permiso, verás el mensaje: Estado HTTP 403 - Access is denied, dedicaremos un par de minutos a estudiar la manera de personalizar este mensaje y mostrar una página más adecuada.

personalizar página de acceso denegado

Lo primero que haremos será diseñar nuestra nueva página de acceso denegado, la llamaremos denegado.jsp, para nuestros motivos la misma solo mostrará un mensaje indicando en nombre del usuario más el mensaje que le indique que no tiene permisos para ver un contenido determinado.

/WEB-INF/views/denegado.jsp

<%@ page contentType="text/html" pageEncoding="UTF-8" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>

        <h1>Acceso denegado</h1>

        <p><b><sec:authentication property="principal.username"/></b>, Usted no tiene permiso para ver este contenido.</p>
        
        <form action="${pageContext.servletContext.contextPath}/logout" method="post">
            <input type="submit" value="logout"/>
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
        </form>

    </body>
</html>

Usando <sec:authentication property="principal.username"/> obtenemos el nombre del usuario actual, este es un Spring Security Taglib que ya hemos visto en tutoriales previos, en la parte final agregamos un botón a hacer logout, el mismo utiliza la protección CSRF.

El asunto ahora es indicarle a nuestra aplicación que debe utilizar esta página para responder al error 403 (acceso denegado), para ello vamos a la configuración y hacemos lo siguiente:

@Configuration
@EnableWebSecurity
public class WebSecConfig extends WebSecurityConfigurerAdapter {

    // ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/ventas").hasAuthority("ROLE_ADMIN")
                    .anyRequest().authenticated()
                .and()
                    .formLogin()
                    .loginPage("/login")
                    .permitAll()
                .and()
                    .logout().permitAll()
                .and()
                    .exceptionHandling()
                    .accessDeniedPage("/accessdenied");       
    }
}

Le prestamos mayor atención al método accessDeniedPage("...") el cual usaremos para indicar la URL a la seremos redirigidos cuando se nos deniegue el permiso de ver alguna página.

.and()
   .exceptionHandling()
   .accessDeniedPage("/accessdenied");

Ahora debemos agregar un controlador que maneje las peticiones a esta URL, lo hacemos de igual modo que hemos visto en toda nuestra serie de tutoriales, este es el controlador:

@Controller
public class HomeController {

    @RequestMapping("/ventas")
    public String ventas(Model model) {
        return "ventas";
    }
    
    @RequestMapping("/accessdenied")
    public String accessdenied(Model model) {
        return "denegado";
    }
}

Usando la anotación @RequestMapping atendemos la petición a la dirección indicada, podemos ver que devolvemos el nombre de la página JSP que será usada para generar la vista final, de este modo el siguiente código genera la vista usando la página previamente creada.

@RequestMapping("/accessdenied")
public String accessdenied(Model model) {
     return "denegado";
}

Para probar la aplicación primero registra un usuario y asígnale el ROLE_USER, luego inicia sesión con dicho usuario, al hacerlo iras a la página de inicio en ella podrás ver un enlace que te permitirá ir a la página de ventas, al misma solo puede ser vista por usuarios con ROLE_ADMIN por lo que deberás ver la página de acceso denegado que acabamos de crear en lugar de la página por defecto.

Cambiar página de acceso denegado

Usar la interface AccessDeniedHandler

Si deseamos tener mas control de lo que ocurre cuando se le niega el permiso de acceso a un usuario debemos implementar la interface AccessDeniedHandler, con ella solo debemos implementar el método handle(), en él agregamos la lógica para tratar la denegación del permiso y luego redirigimos a la página que ya hemos creado previamente denegado.jsp.

Por ejemplo:

public class MyAccessDeniedHandlerImpl implements AccessDeniedHandler {

    private static final Logger LOG = Logger.getLogger(MyAccessDeniedHandlerImpl.class.getName());

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ade)
            throws IOException, ServletException {

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        if (auth != null) {
            LOG.log(Level.WARNING, "User: {0} attempted to access the protected URL: {1}", 
                    new Object[]{ auth.getName(), request.getRequestURI() });
        }

        // redirigir a la pagina de acceso denegado
        response.sendRedirect(request.getContextPath() + "/accessdenied");
    }

}

Este ejemplo no hace mucho, solo agrega un mensaje al LOG indicando que un determinado usuario a intentado acceder a una URL a la que no tiene permiso.

Para utilizar esta clase solo debemos modificar la configuración de la siguiente manera:

.and()
   .exceptionHandling()
   .accessDeniedHandler(accessDeniedHandler());

El método accessDeniedHandler() devuelve el bean respectiva a la implementación de la interface que acabamos de crear.

@Bean
public AccessDeniedHandler accessDeniedHandler() {
    return new MyAccessDeniedHandlerImpl();
}

Si probamos la aplicación nuevamente veremos lo mismo, solo que esta vez el LOG llevara el registro de los usuarios que han intentado acceder a un URL no permitida.

Descargar proyecto: springsecurity-accesodenegado.zip

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

tkinter Canvas

Histogramas OpenCV Python

Python Binance API