Is it possible to use Context annotation and RolesAllowed annotation in a JAX-RS resource with Apache CXF 2.4.6 and Spring Security 3.2.8?
My CXF configuration:
<jaxrs:server address="/example">
<jaxrs:serviceBeans>
<ref bean="myResourceImpl"/>
</jaxrs:serviceBeans>
</jaxrs:server>
My Java source code:
@Path("/myresource")
public interface MyResource {
@GET
@Produces(MediaType.TEXT_XML)
String get();
}
@Named
public class MyResourceImpl implements MyResource {
@Context
private SecurityContext securityContext;
@Override
@RolesAllowed("ROLE_user")
public String get() {
return securityContext.getUserPrincipal().getName();
}
}
After starting the server, I get the following exception:
Caused by: java.lang.IllegalArgumentException: Can not set javax.ws.rs.core.SecurityContext field MyResourceImpl.securityContext to com.sun.proxy.$Proxy473
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:164)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:168)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:55)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:75)
at java.lang.reflect.Field.set(Field.java:741)
at org.apache.cxf.jaxrs.utils.InjectionUtils$1.run(InjectionUtils.java:164)
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.cxf.jaxrs.utils.InjectionUtils.injectFieldValue(InjectionUtils.java:160)
at org.apache.cxf.jaxrs.utils.InjectionUtils.injectContextProxiesAndApplication(InjectionUtils.java:912)
at org.apache.cxf.jaxrs.JAXRSServerFactoryBean.injectContexts(JAXRSServerFactoryBean.java:354)
at org.apache.cxf.jaxrs.JAXRSServerFactoryBean.updateClassResourceProviders(JAXRSServerFactoryBean.java:380)
at org.apache.cxf.jaxrs.JAXRSServerFactoryBean.create(JAXRSServerFactoryBean.java:145)
... 59 more
If I remove one of the annotations, it works fine.
The problem seems to be that Spring creates a proxy and Apache CXF cannot inject that proxy with the SecurityContext.
I have to use Spring Security and cannot use container-based security.
I found four work-arounds:
Extended Interface
@Path("/myresource")
public interface MyResource {
@Context
public void setSecurityContext(Security securityContext);
@GET
@Produces(MediaType.TEXT_XML)
String get();
}
@Named
public class MyResourceImpl implements MyResource {
private SecurityContext securityContext;
@Override
public void setSecurityContext(Security securityContext) {
this.securityContext = securityContext
}
@Override
@RolesAllowed("ROLE_user")
public String get() {
return securityContext.getUserPrincipal().getName();
}
}
But this solution is not perfect, because my client should not see implementation details.
If I add a second interface with a public setter for SecurityContext
, Apache CXF could inject the JDK proxy with SecurityContext
.
public interface ContextAware {
@Context
public void setSecurityContext(Security securityContext);
}
@Path("/myresource")
public interface MyResource {
@GET
@Produces(MediaType.TEXT_XML)
String get();
}
@Named
public class MyResourceImpl implements MyResource, ContextAware {
private SecurityContext securityContext;
@Override
public void setSecurityContext(Security securityContext) {
this.securityContext = securityContext
}
@Override
@RolesAllowed("ROLE_user")
public String get() {
return securityContext.getUserPrincipal().getName();
}
}
If I remove the interface Spring uses a CGLIB proxy.
@Named
@Path("/myresource")
public class MyResourceImpl {
@Context
private SecurityContext securityContext;
@RolesAllowed("ROLE_superadmin")
@GET
@Produces(MediaType.TEXT_XML)
public String get() {
return securityContext.getUserPrincipal().getName();
}
}
But this solution is not good, because my client should not see implementation details. And my client should not need implementation dependencies.
@Path("/myresource")
public interface MyResource {
@GET
@Produces(MediaType.TEXT_XML)
String get();
}
@Named
public class MyResourceImpl implements MyResource {
@Context
private SecurityContext securityContext;
@Override
@RolesAllowed("ROLE_user")
public String get() {
return securityContext.getUserPrincipal().getName();
}
}