with tutorial example ejemplo java spring spring-boot spring-security

java - tutorial - Spring Security tienePermission para la recopilación<Object>



spring-security-config (3)

Tengo una aplicación que funciona asegurada con seguridad a nivel de método:

RestController:

@PreAuthorize("hasPermission(#product, ''WRITE'')") @RequestMapping(value = "/save", method = RequestMethod.POST) public Product save(@RequestBody Product product) { return productService.save(product); }

Permiso Evaluador:

public class SecurityPermissionEvaluator implements PermissionEvaluator { private Logger log = LoggerFactory.getLogger(SecurityPermissionEvaluator.class); private final PermissionService permissionService; public SecurityPermissionEvaluator(PermissionService permissionService) { this.permissionService = permissionService; } @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString()); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { // almost the same implementation } }

Y todo funciona bien hasta que implemente API que guarda la colección de objetos. La lógica de este servicio es actualizar las entidades existentes y / o crear nuevas entidades.

@PreAuthorize("hasPermission(#products, ''WRITE'')") @RequestMapping(value = "/saveCollection", method = RequestMethod.POST) public Collection<Product> save(@RequestBody Collection<Product> products) { return productService.save(products); }

Después de esto, mi servicio de permisos maneja el objeto de colección y se ve así ahora:

PemissionService:

public class PermissionService { public boolean isAuthorized(User user, Object targetDomainObject, String permission) { if (targetDomainObject instanceof TopAppEntity) { if (((TopAppEntity) targetDomainObject).getId() == null) { // check authorities and give response } else { // check ACL and give response } } else if(targetDomainObject instanceof Collection) { boolean isAuthorized = false; Collection targetDomainObjects = (Collection) targetDomainObject; for (Object targetObject : targetDomainObjects) { isAuthorized = isAuthorized(user, targetObject, permission); if (!isAuthorized) break; } return isAuthorized; } } }

Mi pregunta es:

¿Cómo puedo manejar colecciones usando @PreAuthorize("hasPermission(#object, ''...'')") una forma más elegante? ¿Hay algunas implementaciones en Spring Security para manejar colecciones? Al menos, ¿cómo puedo optimizar PemissionService para manejar Collections ?


Puede usar la anotación @PreFilter .

Entonces @PreFilter("hasPermission(filterTarget, ''...'')") llamará a su PermissionService para cada elemento de la Colección.

public class PermissionService() { public boolean isAuthorized(User user, Object targetDomainObject, String permission) { if (targetDomainObject instanceof TopAppEntity) { if (((TopAppEntity) targetDomainObject).getId() == null) { // check authorities and give response } else { // check ACL and give response } } } }

Nota: esto no evitará una llamada de su método de controlador. Solo obtiene una Colección vacía.


Tengo un par de soluciones.

1. El primero es usar mi propio MethodSecurityExpressionHandler y MethodSecurityExpressionRoot .

Creando una CustomMethodSecurityExpressionRoot y define un método que será nuestra nueva expresión para el manejo de la Collection . Extenderá SecurityExpressionRoot para incluir expresiones predeterminadas:

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { private final PermissionEvaluator permissionEvaluator; private final Authentication authentication; private Object filterObject; private Object returnObject; private Object target; public CustomMethodSecurityExpressionRoot(Authentication authentication, PermissionEvaluator permissionEvaluator) { super(authentication); this.authentication = authentication; this.permissionEvaluator = permissionEvaluator; super.setPermissionEvaluator(permissionEvaluator); } public boolean hasAccessToCollection(Collection<Object> collection, String permission) { for (Object object : collection) { if (!permissionEvaluator.hasPermission(authentication, object, permission)) return false; } return true; } @Override public void setFilterObject(Object filterObject) { this.filterObject = filterObject; } @Override public Object getFilterObject() { return filterObject; } @Override public void setReturnObject(Object returnObject) { this.returnObject = returnObject; } @Override public Object getReturnObject() { return returnObject; } @Override public Object getThis() { return target; } }

Crea un controlador de expresiones personalizado e inyecta CustomMethodSecurityExpressionRoot :

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { private final PermissionEvaluator permissionEvaluator; public CustomMethodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) { this.permissionEvaluator = permissionEvaluator; super.setPermissionEvaluator(permissionEvaluator); } @Override protected MethodSecurityExpressionOperations createSecurityExpressionRoot( Authentication authentication, MethodInvocation invocation) { CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication, permissionEvaluator); root.setTrustResolver(new AuthenticationTrustResolverImpl()); root.setRoleHierarchy(getRoleHierarchy()); return root; } }

También introduje SecurityPermissionEvaluator utilizado en cuestión, por lo que será un único punto de entrada para expresiones personalizadas y predeterminadas. Como una opción alternativa, podríamos inyectar y usar PermissionService directamente.

Configurando nuestra seguridad a nivel de método:

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Autowired private PermissionService permissionService; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { PermissionEvaluator permissionEvaluator = new SecurityPermissionEvaluator(permissionService); return new CustomMethodSecurityExpressionHandler(permissionEvaluator); } }

Ahora podemos usar una nueva expresión en RestController :

@PreAuthorize("hasAccessToCollection(#products, ''WRITE'')") @RequestMapping(value = "/saveCollection", method = RequestMethod.POST) public Collection<Product> save(@RequestBody Collection<Product> products) { return productService.save(products); }

Como resultado, se podría omitir una parte con el manejo de la colección en PermissionService ya que sacamos esta lógica a la expresión personalizada.

2. La segunda solución es llamar al método directamente usando SpEL.

Ahora estoy usando PermissionEvaluator como Spring Bean (cualquier servicio podría usarse aquí, pero prefiero un único punto de entrada)

@Component public class SecurityPermissionEvaluator implements PermissionEvaluator { @Autowired private PermissionService permissionService; @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { if (!(targetDomainObject instanceof TopAppEntity)) throw new IllegalArgumentException(); CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString()); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); try { return permissionService.isAuthorized(userDetails.getUser(), targetId, Class.forName(targetType), String.valueOf(permission)); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("No class found " + targetType); } } public boolean hasPermission(Authentication authentication, Collection<Object> targetDomainObjects, Object permission) { for (Object targetDomainObject : targetDomainObjects) { if (!hasPermission(authentication, targetDomainObject, permission)) return false; } return true; } }

Configurando la seguridad del método:

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Autowired private PermissionEvaluator permissionEvaluator; @Autowired private ApplicationContext applicationContext; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); expressionHandler.setPermissionEvaluator(permissionEvaluator); // Pay attention here, or Spring will not be able to resolve bean expressionHandler.setApplicationContext(applicationContext); return expressionHandler; } }

Uso del servicio en expresión:

@PreAuthorize("@securityPermissionEvaluator.hasPermission(authentication, #products, ''WRITE'')") @RequestMapping(value = "/saveCollection", method = RequestMethod.POST) public Collection<Product> save(@RequestBody Collection<Product> products) { return productService.save(products); }

Frijoles de primavera creados por defecto con nombre de clase si no se especifica ningún otro nombre.

Resumen: ambos enfoques basados ​​en el uso de servicios personalizados llamándolos directamente o registrándolos como expresiones y podrían manejar la lógica de la recopilación antes de que se envíe al servicio de comprobación de autoridad, por lo que podemos omitir la parte de la misma:

@Service public class PermissionService { public boolean isAuthorized(User user, TopAppEntity domainEntity, String permission) { // removed instanceof checks and can operate on domainEntity directly if (domainEntity.getId() == null) { // check authorities and give response } else { // check ACL and give response } } }


Sí, hay una manera inteligente. Puedo decirte lo que hice.

@Component("MySecurityPermissionEvaluator ") @Scope(value = "session") public class PermissionService { @Autowired private PermissionEvaluator permissionEvaluator; public boolean myPermission(Object obj, String permission) { boolean isAuthorized = false; Authentication a = SecurityContextHolder.getContext() .getAuthentication(); if (null == obj) { return isAuthorized; } if (a.getAuthorities().size() == 0) { logger.error("For this authenticated object, no authorities could be found !"); return isAuthorized; } else { logger.error("Authorities found " + a.getAuthorities()); } try { isAuthorized = myPermissionEval .hasPermission(a, obj, permission); } catch (Exception e) { logger.error("exception while analysisng permissions"); } return isAuthorized; }

Por favor, no use permisos codificados, use de esta manera en su lugar,

import org.springframework.security.acls.domain.DefaultPermissionFactory; public class MyPermissionFactory extends DefaultPermissionFactory { public MyPermissionFactory() { registerPublicPermissions(MyPermission.class); } }

Para hacer permisos personalizados,

import org.springframework.security.acls.domain.BasePermission; public class MyPermission extends BasePermission { //use this class for creating custom permissions private static Map<String, Integer> customPerMap = new HashMap<String, Integer>(); static { customPerMap.put("READ", 1); customPerMap.put("WRITE", 2); customPerMap.put("DELETE", 4); customPerMap.put("PUT", 8); } /** *Use the function while saving/ getting permission code **/ public static Integer getCode(String permName) { return customPerMap.get(permName.toUpperCase()); }

Si necesita autenticar urls basados ​​en usuarios administrativos o jerarquía de roles, use la etiqueta en Autenticación de primavera, no en Autorización.

Descansa, estás usando correctamente, @PreAuthorize y @PreFilter son correctos y se usan según los requisitos.