Spring Security - Control de Permisos de Url Dinámico



En un primer post mostramos como crear una aplicación con Spring Boot + Thymelaf + BooStrap + MySQL.  En un segundo post mostramos como generar reportes con JasperReports.

En este último post vamos a ver como agregar el proyecto Spring Security para manejar usuarios, roles, urls y permisos de forma dinámica a dichos urls sin necesidad de cambiar el código, todo trabajado desde la base de datos.

Código Fuente:  https://github.com/joedayz/gestionclientes


Configurando Spring Security

  • En el proyecto agregaremos la dependencia Starter de Spring Security. 



  • Empezaremos con la página de Login donde tendremos un formulario y enviaremos el username y password.


Configuración de Seguridad


Para que la seguridad funcione hay que crear un archivo de configuración Java denominado SecurityConfig.java en donde tendremos lo siguiente:


La clase es anotada con @EnableWebSecurity para permitir la integración entre Spring Security y Spring MVC. Este también hereda de WebSecurityConfigurerAdapter y te invita a sobreescribir un par de métodos para establecer configuración específica de seguridad web. 

@EnableGlobalMethodSecurity te permite habilitar seguridad glogal a nivel de métodos. 
  • El prePostEnable es una propiedad que permite anotaciones pre/post de seguridad.
  • proxyTargetClass es una propiedad  que indica que se usará proxies basados en CGLIB como alternativa a proxies basado en interfaces Java. La ventaja está explicada en este link.
  • @Import({CustomAuthorizationConfig.class}) permite tener configuración adicional en otro archivo y unirla con la configuración actual. 
  • @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER):  Para sobreescribir las reglas de acceso sin cambiar ninguna caracteristica auto configurada que viene por spring boot por defecto (más información en la documentación).

Mi CustomUserAuthenticationProvider


En Spring Security se pueden declarar varios Filtros para personalizar tu seguridad.



El primero filtro a implementar es el AuthenticationProvider y es una interface de Spring Security que nos permite implementar nuestra propia autenticación.


@Autowired
private AuthenticationProvider customUserAuthenticationProvider;

Entonces tengo que implementar dicha interface. A continuación la primera parte de la implementación:


Nos aseguramos que tenga la dependencias necesarias, cómo: Un UsuarioRepository para consultar la tabla Usuarios y una clase para consultar el Password acorde a la encriptación BCrypt que nos provee Spring.

Esta interface nos pide implementar 2 métodos:




En este método vemos si el usuario existe en la BD. Sino arrojamos una excepción UsernameNotFoundException. También validamos que el password corresponda y de no ser así arrojamos una excepción BadCredentialsException.







Esta clase que es invocada en dicho método se encarga de obtener los roles del usuario y devolverlos como una colección de GrantedAuthority

 Gracias a que vía JPA tenemos mapeado los usuario y roles podemos hacer eso sin problema.
El método final a implementar es este donde especificamos que la clase es de tipo UsernamePasswordAuthenticationToken. @Overridepublic boolean supports(Class authentication) { return UsernamePasswordAuthenticationToken.class.equals(authentication); } La tabla Usuario se verá de esta forma:

El AccessDecisionManager


Esta se encarga de la autorización. En mi caso no cambiare nada y tomare la autorización basada en roles.



Roles que tendrán la forma ROLE_XXX. Por eso guardaremos los Roles en esta tabla:





CustomWebSecurityExpressionHandler


Esta clase es para declarar que manejaremos la seguridad web basada en roles (getRoleHierarchy()) y permisos basado en expressiones (getPermissionEvaluator()).


@Componentpublic class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {
   private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

   @Override   protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
      WebSecurityExpressionRoot root = new CustomWebSecurityExpressionRoot(authentication, fi);
      root.setPermissionEvaluator(getPermissionEvaluator());
      root.setTrustResolver(trustResolver);
      root.setRoleHierarchy(getRoleHierarchy());
      return root;
   }
}


FilterInvocationServiceSecurityMetadataSource

Implementar esta interface nos va a permitir que podamos tener de forma dinámica los URLs y los ROLES que pueden acceder a ellos.


Implementando InitializingBean nos permite al implementar afterPropertiesSet tener un método init() en nuestro bean.

Y como se puede apreciar en este método:


@Overridepublic void afterPropertiesSet() throws Exception {
   List<RequestConfigMapping> requestConfigMappings = requestConfigMappingService.getRequestConfigMappings();
   LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>(requestConfigMappings.size());
   for(RequestConfigMapping requestConfigMapping : requestConfigMappings) {
      RequestMatcher matcher = requestConfigMapping.getMatcher();
      requestMap.put(matcher,requestConfigMapping.getAttributes());
   }
   this.delegate = new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, expressionHandler);
}

El requestConfigMappingService nos da la relación entre URLs y permisos. Por lo tanto, implementamos JdbcRequestConfigMappingService implements RequestConfigMappingService



Y aquí el query que trae la relación:

private static final String SELECT_SQL = "select ant_pattern, expression from Filter_Metadata order by sort_order";



SecurityConfig

Implementando estas interfaces logramos que nuestra configuración sea minimalista y USUARIOS, ROLES, USUARIOS_ROLES, FILTERSECURITY_METADA (Urls y Roles) se manejen por base de datos.

Quedando nuestra clase de esta manera:




Ignoramos la seguridad para ciertos recursos y


Con esto terminamos nuestra demo de Clientes ( https://github.com/joedayz/gestionclientes) y ya puede manejar la seguridad con Spring Security.

Enjoy!


Joe


Share:

5 comentarios:

  1. Hola, para ejecutar el ejemplo tuve que hacer algun ajuste menor, pongo en respuesta a este comentario lo que hice. Algunas cosas deberían ir al Git, y el como arrancar la BD en un MySQL en Docker quizás en el README.me como tutorial. Descargué el código en zip y no puedo hacer pull request, lo siento.

    ResponderBorrar
  2. Este comentario ha sido eliminado por el autor.

    ResponderBorrar
  3. Puf, menudo embrollo, mejor te mando los comentarios directamente.

    ResponderBorrar