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.