I am trying to make work Jersey 3.1.1 with Spring AOP 6.0.13 for Spring security authorization. So, I have two REST resources:
@Component
@Path("/foo")
public class FooResource {
private final MyBean bean;
@Autowired
public FooResource(MyBean bean) {
this.bean = bean;
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/test")
public String test(@Context HttpServletRequest req) {
System.out.print("FOO RESOURCE: ================================:");
System.out.println(this.bean.getIt());
return "<body>Hello! This is foo.test rest</body>";
}
}
@Component
@Path("/bar")
public class BarResource implements ResourceInterface {
private final MyBean bean;
@Autowired
public BarResource(MyBean bean) {
this.bean = bean;
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/test")
public String test(@Context HttpServletRequest req) {
System.out.print("BAR RESOURCE: ================================:");
System.out.println(this.bean.getIt());
return "<body>Hello! This is bar.test rest</body>";
}
@Override
public void doSomething() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
The only difference between these two resources is that BarResource
implements some interface while FooResource
doesn't implement any interfaces. For authorization I have the following code in SecurityConfig:
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor authorizeFoo() {
System.out.println("SecurityConfig FooResource Point ################### ========================");
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
pattern.setPattern("com.foo.FooResource.*");
AuthorizationManager<MethodInvocation> rule = new AuthorizationManagerImpl();
AuthorizationManagerBeforeMethodInterceptor interceptor =
new AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() - 1);
return interceptor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor authorizeBar() {
System.out.println("SecurityConfig BarResource Point ################### ========================");
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
pattern.setPattern("com.foo.BarResource.*");
AuthorizationManager<MethodInvocation> rule = new AuthorizationManagerImpl();
AuthorizationManagerBeforeMethodInterceptor interceptor =
new AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() - 1);
return interceptor;
}
As you see the adding authorization for two resources is the same. However, when we run test (using mvn clean jetty:run-war
) we will get the following log messages from Jersey:
[main] ERROR org.glassfish.jersey.server.spring.SpringComponentProvider - None or multiple beans found in Spring context for type class com.foo.BarResource, skipping the type.
[main] INFO org.glassfish.jersey.server.spring.SpringComponentProvider - Spring managed bean, fooResource, registered with HK2.
And if we open http://127.0.0.1:8080/rest/foo/test
we will see that AuthorizationManager is called. But if we open http://127.0.0.1:8080/rest/bar/test
then we will see that AuthorizationManager is NOT called.
I think that the problem is in Spring AOP because if we comment/remove authorizeFoo()
and authorizeBar()
methods from SecurityConfig
then both services work as expected (without authorization of course) and Jersey will give the following log messages:
[main] INFO org.glassfish.jersey.server.spring.SpringComponentProvider - Spring managed bean, fooResource, registered with HK2.
[main] INFO org.glassfish.jersey.server.spring.SpringComponentProvider - Spring managed bean, barResource, registered with HK2.
Here is the link to Jersey code that gives the above error. Here is a link to a test project with all code and readme file.
Such strange behavior makes problem and it seems to me that this is a bug.
Comment From: snicoll
@PavelTurk thanks for the sample but rather than creating a duplicate, using the existing issue would have been better.
It's hard to see if something here needs to be done considering the Spring integration for Jersey isn't managed here. You mentioned an upgrade to Spring 6 but you also upgraded to jersey-spring6
that's out of our control. The error you've shared is from the Jersey Spring integration.
As for the sample, I could see the error in the logs but the endpoints work properly. What am I supposed to see in step 2 and 3?
Comment From: PavelTurk
@snicoll Thank you for your answer. If i had thought that this is the Jersey problem I would have opened the issue in Jersey project, right? I explained in my issue why I think that the problem is in Spring AOP:
I think that the problem is in Spring AOP because if we comment/remove authorizeFoo() and authorizeBar() methods from SecurityConfig then both services work as expected (without authorization of course) and Jersey will give the following log messages:
What I expect is explanation that this is a Jersey problem or fixing the issue if the problem is in Spring.
Comment From: PavelTurk
As for the sample, I could see the error in the logs but the endpoints work properly.
Sorry, I missed that. Please, note, that if we open http://127.0.0.1:8080/rest/foo/test we will see that AuthorizationManager IS called. But if we open http://127.0.0.1:8080/rest/bar/test then we will see that AuthorizationManager IS NOT called.
AuthorizationManagerImpl is :
private static class AuthorizationManagerImpl implements AuthorizationManager<MethodInvocation> {
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation object) {
System.out.println("SecurityConfig Check Point ################### ========================");
return new AuthorityAuthorizationDecision(true, new ArrayList<>());
}
}
So, it is easy to see on console when manager is called and when is not.
Comment From: snicoll
If i had thought that this is the Jersey problem I would have opened the issue in Jersey project, right?
Certainly. I was referring to the fact that the Jersey container is managing those resources and that the error is issued by the Jersey integration. Both of these combined doesn't make it obvious to me that the issue is not there. If it works if you remove the component that creates the proxy doesn't prove anything, I am afraid.
What I expect is explanation that this is a Jersey problem or fixing the issue if the problem is in Spring.
The latter certainly.
So, it is easy to see on console when manager is called and when is not.
Alright. I'll look at it a bit more when time permits.
Comment From: PavelTurk
Alright. I'll look at it a bit more when time permits.
@snicoll Thank you very much.
It took me a lot of time to find out the reason why some resources work and some don't. So, I want to solve this problem.
Also it can be very dangerous from security reason - a developer will think that all his resources are protected while they are not.
Comment From: mdeinum
Just chiming in here, but if there is an @EnableAspectJAutoProxy
make sure that you are enforcing class proxies, else Spring will create a interface based proxy. The same limitations (or note) applies to Spring MVC and is documented in the reference guide (https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann.html#mvc-ann-requestmapping-proxying). The same limitation will apply here.
Related: #26603
Comment From: snicoll
Yup it kind of is that, indeed.
@PavelTurk your BarResource
was exposed as a ResourceInterface
and the BarResource
type disappeared, which explains why Jersey couldn't find it. This applied also for method security. If you have both an interface like that + the aspect needs to wrap the implementation, you need to turn on CGLIB proxy.
I've changed your SecurityConfig
to @EnableMethodSecurity(proxyTargetClass = true)
and that did the trick.