Spring doesn't see @Transactional when global security namespace is added

I created a service responsible for contacting the database through dao. I used the @Transactional annotation to process transactions.

 @Service("aclService") public class HibernateAclServiceImpl implements HibernateAclService{ private final Log logger = LogFactory.getLog(HibernateAclServiceImpl.class); @Autowired private AclObjectIdentityDao objectIdentityDao ; private PermissionFactory permissionFactory = new DefaultPermissionFactory(); @Autowired private AclCache aclCache; @Autowired private PermissionGrantingStrategy grantingStrategy; @Autowired private AclAuthorizationStrategy aclAuthorizationStrategy; private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); @Override @Transactional public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) { AclObjectIdentity aclObjectIdentity = objectIdentityDao .get((Long) parentIdentity.getIdentifier()); List<ObjectIdentity> list = new ArrayList<ObjectIdentity>( aclObjectIdentity.getChildren().size()); for (AclObjectIdentity aoid : aclObjectIdentity.getChildren()) { final ObjectIdentity oid = new ObjectIdentityImpl(aoid.getObjectClass().getClazz()); list.add(oid); } return list; } @Override @Transactional public Acl readAclById(ObjectIdentity object) throws NotFoundException { final Map<ObjectIdentity, Acl> objects = readAclsById(Arrays.asList(object), null); return objects.get(object); } @Override @Transactional public Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException { Map<ObjectIdentity, Acl> objects = readAclsById(Arrays.asList(object), sids); return objects.get(object); } @Override @Transactional public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects) throws NotFoundException { return readAclsById(objects, null); } @Override public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) throws NotFoundException { Map<ObjectIdentity, Acl> result = new HashMap<ObjectIdentity, Acl>(); Set<Long> objectsToLoad = new HashSet<Long>(); for (int i = 0; i < objects.size(); i++) { final ObjectIdentity oid = objects.get(i); boolean aclFound = false; if (result.containsKey(oid)) { aclFound = true; } if (!aclFound) { Acl acl = aclCache.getFromCache(oid); if (acl != null) { if (acl.isSidLoaded(sids)) { result.put(acl.getObjectIdentity(), acl); aclFound = true; } else { throw new IllegalStateException( "Error: SID-filtered element detected when implementation does not perform SID filtering " + "- have you added something to the cache manually?"); } } } if (!aclFound) { objectsToLoad.add((Long) oid.getIdentifier()); } } if (objectsToLoad.size() > 0) { lookupAcl(result, objectsToLoad); } return result; } public void lookupAcl(Map<ObjectIdentity, Acl> map, Set<Long> objects){ final List<AclObjectIdentity> aoids = objectIdentityDao.getList(objects); final Map<Long, Long> parents = new HashMap<Long, Long>(); for(AclObjectIdentity aoid : aoids){ if(aoid.isEntriesInheriting()){ parents.put(aoid.getId(), aoid.getParent().getId()); } } if(parents.size() > 0){ lookupAcl(map, (Set<Long>)parents.values()); } for(AclObjectIdentity aoid : aoids){ if(map.containsKey(aoid.getId())) continue; final Acl parentAcl = map.get(parents.get(aoid.getId())); final Acl acl = new AclImpl(new ObjectIdentityImpl(aoid.getObjectClass().getClazz(), aoid.getId()), aoid.getId(), aclAuthorizationStrategy, grantingStrategy, parentAcl, null, aoid.isEntriesInheriting(), new PrincipalSid(aoid.getOwnerSid().getSid())); List<AccessControlEntryImpl> aces = new ArrayList<AccessControlEntryImpl>(aoid.getAclEntries().size()); for(AclEntry aclEntry : aoid.getAclEntries()){ final Permission permission = permissionFactory.buildFromMask(aclEntry.getMask()); aces.add(new AccessControlEntryImpl(aclEntry.getId(), acl, new PrincipalSid(aclEntry.getSid().getSid()), permission, aclEntry.isGranting(), aclEntry.isAuditSuccess(), aclEntry.isAuditFailure())); } setAces((AclImpl) acl, aces); aclCache.putInCache((AclImpl) acl); } } private void setAces(AclImpl acl, List<AccessControlEntryImpl> aces) { try { fieldAces.set(acl, aces); } catch (IllegalAccessException e) { throw new IllegalStateException("Could not set AclImpl entries", e); } } 

}

Here is part of my app-context.xml file

 <security:global-method-security pre-post-annotations="enabled"> <security:expression-handler ref="expressionHandler" /> </security:global-method-security> <bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <property name="permissionEvaluator" ref="permissionEvaluator" /> <property name="roleHierarchy" ref="roleHierarchy" /> </bean> <bean class="org.springframework.security.acls.AclPermissionEvaluator" id="permissionEvaluator"> <constructor-arg ref="aclService" /> </bean> 

Now when I call the service function, for example. from the controller it throws an org.hibernate.HibernateException: No Session found for current thread error. But everything works fine (no transaction problems) when I comment

<security:global-method-security pre-post-annotations="enabled"> <security:expression-handler ref="expressionHandler" /> </security:global-method-security>

I checked everything and I narrowed down the problematic code with the above piece. Does anyone have an idea why this is happening?

+4
source share
1 answer

I’m not sure how global-method-security is implemented under the covers, but there is a little known side effect of BeanPostProcessors - any bean referenced directly by BeanPostProcessor , or refers to something like BPP, are not allowed to auto-proxy AOP:

BeanPostProcessors and AOP Auto-Proxing

Classes that implement the BeanPostProcessor interface are special and are handled differently by the container. All BeanPostProcessors and beans that they reference directly are created at startup as part of the special ApplicationContext launch phase. Then all BeanPostProcessors are registered in sort order and applied to all beans in the container. Since AOP automatic proxying is implemented as BeanPostProcessor itself, neither BeanPostProcessors nor the beans that they reference directly have the right to automatic proxying and, therefore, do not have aspects woven into them.

For any such bean, you should see an informational log message: "Bean foo is not suitable for processing by all BeanPostProcessor interfaces (for example: not suitable for automatic proxying)."

(a source)

This means that if you have @Transactional in a bean that is loaded into a link to a BeanPostProcessor, this annotation is ignored.

The solution would usually be that if you need transactional behavior in the bean that needs to be loaded into the BeanPostProcessor link, you will need to use transaction definitions other than AOP, i.e. use TransactionTemplate.

You can enable logging on org.springframework loggers on DEBUG and check if this message is displayed for your aclService bean.

+7
source

Source: https://habr.com/ru/post/1436440/


All Articles