I want to implement an authorization method by using Spring Boot AOP. The original idea is, if the return object return from the REST calls didn't pass the authorization check, it will throw a unauthorized exception.
I'm doing it like this:
@Aspect
@Component
public class AuthAspect {
@Around("AllRestExecPoint()")
public Object auth(ProceedingJoinPoint point) throws Throwable {
Object returnObject = point.proceed();
if (!checkAuthorization(returnObject)) {
throw new UnauthException();
}
return returnObject;
}
}
However, the problem is, if this REST service will execute some INSERT or UPDATE to my DB, it will commit before my authorization checks. Therefore, the UnauthException
will be thrown but the transaction is still committed.
The first try I want to manually create transaction before the proceed()
call and commit it before return, but it failed.
@Aspect
@Component
public class AuthAspect {
private final EntityManager em;
@Autowired
public AuthAspect(EntityManager em) {
this.em = em;
}
@Around("AllRestExecPoint()")
public Object auth(ProceedingJoinPoint point) throws Throwable {
em.getTransaction().begin();
Object returnObject = point.proceed();
if (!checkAuthorization(returnObject)) {
throw new UnauthException();
}
em.getTransaction().commit();
return returnObject;
}
}
It will cause the java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
.
I searched on the internet and some answers need to modify web.xml
file, but I don't want to use xml to do the configuration.
Judging from your tags you are using Spring Boot. Spring Boot provides a pre-configured TransactionTemplate
which you can use if you manually want to control transactions.
Instead of the EntityManger
inject this into your aspect and wrap your code in it.
@Aspect
@Component
public class AuthAspect {
private final TransactionTemplate tx;
public AuthAspect(TransactionTemplate tx) {
this.tx = tx;
}
@Around("AllRestExecPoint()")
public Object auth(ProceedingJoinPoint pjp) throws Throwable {
return tx.execute(ts -> this.executeAuth(pjp));
}
private Object executeAuth(ProceedingJoinPoint pjp) {
Object returnObject;
try {
returnObject = pjp.proceed();
} catch (Throwable t) {
throw new AopInvocationException(t.getMessage(), t);
}
if (!checkAuthorization(returnObject)) {
throw new UnauthException();
}
return returnObject;
}
}
This will execute the logic inside a transaction. I moved the actual logic to a method so that the lambda can be a single method instead of code block. (Personal preference/best practice).