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 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 both 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.
Dedicated interface
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();
}
}
CGLIB proxy without interface
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.
CGLIB proxy with interface
#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();
}
}
I took a slight variation on the solution from #dur. Instead of having the #Context as a field, I passed it as a parameter to my method that needed it (I was using SearchContext):
#Path("/myresource")
public interface MyResource {
#GET
#Produces(MediaType.TEXT_XML)
String get(#Context SecurityContext securityContext);
}
#Named
public class MyResourceImpl implements MyResource {
#Override
#RolesAllowed("ROLE_user")
public String get(SecurityContext securityContext) {
return securityContext.getUserPrincipal().getName();
}
}
Related
I'm not sure whether this is possible or not but I'm trying to setup a EJB + JAX-RS (Jersey) test project and use the #RolesAllowed annotation.
I'm currently getting the following error logs:
Warning: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
Severe: ejb.stateless_ejbcreate_exception
Warning: A system exception occurred during an invocation on EJB TestSB, method: public java.util.List rest.sb.TestSB.findAll()
Warning: javax.ejb.EJBException: javax.ejb.EJBException: javax.ejb.CreateException: Could not create stateless EJB
Relevant classes:
ApplicationConfig.java
#ApplicationPath("rest")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("rest");
register(RolesAllowedDynamicFeature.class);
}
}
TestSBFacade.java
#Local
public interface TestSBFacade {
public List<Test> findAll();
}
TestSB.java
#Stateless
#Path("secured/test")
public class TestSB implements TestSBFacade {
#DAO #Inject
private TestDAOFacade dao;
#Context
SecurityContext securityContext;
#Secured
#RolesAllowed({"READ"})
#Path("all")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Override
public List<Test> findAll() {
//this works without the #RolesAllowed so it is a possible workaroud for now.
System.out.println(securityContext.isUserInRole("READ")); //output: true
return dao.findAll();
}
}
AuthFilter.java
#Provider
#Secured //NameBinding
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String token = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
try {
verifyToken();
createSecurityContext();
} catch (Exception e) {
Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, "Invalid or Expired JWT");
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
}
My SecurityContext is set and working, the #RolesAllowed seems to be to problem since I get no errors if I remove it and a JSON is properly returned to the front-end. Keeping the #RolesAllowed results in the errors mentioned at the start.
However I'd like to use the handy annotation instead of embedding every method inside isUserInRole IF Statements. Any help and insights are much appreciated.
So apparently due to both EJB and JAX-RS implementations using#RolesAllowed they don't do well together. So I decided to create my own Annotation instead and register my own DynamicFeature in the ApplicationConfig.java.
Authorized.java
#Documented
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Authorized {
public String[] value() default "";
}
AuthorizationDynamicFeature.java
public class AuthorizationDynamicFeature implements DynamicFeature {
#Override
public void configure(final ResourceInfo resourceInfo, final FeatureContext featureContext) {
Authorized auth = new AnnotatedMethod(resourceInfo.getResourceMethod()).getAnnotation(Authorized.class);
if (auth != null) {
featureContext.register(new AuthorizationRequestFilter(auth.value()));
}
}
#Priority(Priorities.AUTHORIZATION)
private static class AuthorizationRequestFilter implements ContainerRequestFilter {
private final String[] roles;
AuthorizationRequestFilter() {
this.roles = null;
}
AuthorizationRequestFilter(final String[] roles) {
this.roles = roles;
}
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
if (!this.roles[0].isEmpty()) {
for (final String role : this.roles) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
throw new ForbiddenException(LocalizationMessages.USER_NOT_AUTHORIZED());
}
}
}
}
Huge thanks to #PaulSamsotha for leading me to a more suitable solution.
I have following base resource (endpoint) test class.
public abstract class AbstractResourceTest extends JerseyTest {
private static final String PORT = "9991";
#Override
protected TestContainerFactory getTestContainerFactory() {
return new GrizzlyWebTestContainerFactory();
}
#Override
protected DeploymentContext configureDeployment() {
forceSet(TestProperties.CONTAINER_PORT, PORT);
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
final ResourceConfig config = new ResourceConfig().packages(
"com.intouch.api.rest",
"com.intouch.api.security")
.register(createMoxyJsonResolver())
.register(SecurityEntityFilteringFeature.class)
.property("jersey.config.server.tracing.type", "ALL")
.property("jersey.config.server.tracing.threshold", "TRACE")
.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
return ServletDeploymentContext
.forServlet(new ServletContainer(config))
.addListener(ContextLoaderListener.class)
.contextParam("contextConfigLocation", "classpath:applicationContext-api-test.xml")
.build();
}
#Override
protected void configureClient(ClientConfig config) {
super.configureClient(config);
config.register(GsonJsonObjectProvider.class)
.register(GsonJsonArrayProvider.class);
}
}
It's working perfect for tests, which needs role (annotated with #RolesAllowed). But I have some endpoints, which don't need roles. This tests are failing because of for them are check the role of previous test (for example previous test is calling an endpoint which needs MANAGE role, next test is calling an endpoint which don't need any role and for this (second test) is calling securityContext.isUserInRole("MANAGE")).
Is it bug or it should be like this?
How we can fix it?
This is an example of resource (endpoint):
#Path("/users")
#Component
public class UserResource {
#Autowired
private UserServiceFacade userServiceFacade;
#POST
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Consumes(MediaType.APPLICATION_JSON)
public UserAccountBean createUser(UserAccountBean userAccountBean) {
return userServiceFacade.createUser(userAccountBean);
}
#PUT
#RolesAllowed({Permission.Constants.MANAGE_USERS_VALUE})
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Consumes(MediaType.APPLICATION_JSON)
public UserAccountBean updateUser(UserAccountBean userAccountBean) {
return userServiceFacade.updateUser(userAccountBean);
}
}
I am trying to use CDI-like interceptors with EJB on Wildfly 8.2, but they are not invoked in any EJB. However, they are working just fine with CDI objects.
#InterceptorBinding
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DataTransferObject {
}
Interceptor to clone entity to DTO as return;
#Interceptor
#DataTransferObject
public class DataTransferObjectInterceptor {
#AroundInvoke
public Object clone(InvocationContext invocationContext) throws Exception {
Object actual = invocationContext.proceed();
Object clone = actual.getClass().newInstance();
BeanUtil.clone(actual, clone);
return clone;
}
}
The interceptor doesn't catch this;
#Stateless
#DataTransferObject
public class BaseCompanyService implements CompanyService {
#EJB
private CompanyDAO companyDAO;
#Override
public void create(Company entity) throws EntityException {
companyDAO.create(entity);
}
.
.
}
But it is working OK here;
#Path("/company")
#Produces(MediaType.APPLICATION_JSON)
#DataTransferObject
#Slf4j
public class CompanyResource implements Resource {
#EJB
private CompanyService companyService;
#GET
#Path("/check")
#Override
public Success check() {
return new Success("The company service is running.");
}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public Success create(#Valid Company entity) throws EntityException {
companyService.create(entity);
log.info("The company with id \"{}\" is successfully created.", entity.getId());
return new Success("The company is successfully created.");
}
.
.
}
beans.xml
<interceptors>
<class>io.rraa.interceptors.DataTransferObjectInterceptor</class>
</interceptors>
I'm using a container-item pattern and I have:
Container to store users:
#Path("/user")
#Stateless
public class UsersResource {
#Context
private UriInfo context;
#EJB
private UserBeanLocal userBean;
public UsersResource() {
}
#GET
#Produces("application/json")
public String getJson(#HeaderParam("authorization") String authorization) {
return userBean.sampleJSON();
}
#Path("{id}")
public UserResource getUserResource(#PathParam("id") String id) {
return UserResource.getInstance(id);
}
}
Item that is representing single user:
#Stateless
public class UserResource {
#EJB
private UserBeanLocal userBean;
private String id;
public UserResource() {
}
private UserResource(String id) {
this.id = id;
}
public static UserResource getInstance(String id) {
return new UserResource(id);
}
#GET
#Produces("application/json")
public String getJson() {
//TODO return proper representation object
return userBean.sampleJSON();
}
}
And bean. It has only one method:
#Local
public interface UserBeanLocal {
String sampleJSON();
}
#Stateless
public class UserBean implements UserBeanLocal {
#Override
public String sampleJSON() {
return "{\"Name\": \"Jan\", \"Lastname\": \"Węglarz\", \"PESEL\": \"47092412341\"}";
}
}
EJB in container works fine but in item returns null. Why?
I've tried to return in getJson() something else, for example id, and there was no problem. Everything worked fine. But when I'm returning something using EJB there is null exception.
App deploys on jboss 7 without any problem.
That is because you are creating instance of UserResource by yourself:
return UserResource.getInstance(id);
Because it is not created by container via JNDI lookup or injection, it is not container managed. Dependency injection can only take place in container managed components.
I added in UsersResource:
#EJB
private UserResource userResource;
and then changed:
#Path("{id}")
public UserResource getUserResource(#PathParam("id") String id) {
return userResource;
}
And now it works. Thanks :)
I have simple restful WS
#Path("basic")
public class ServiceRS
{
private IServiceJAX service;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String find(#FormParam("searchRequest") final String searchRequest)
{
//...
final List<Info> response = service.find(search);
//...
}
}
Where IServiceJAX is #Local interface of jax-webservice.
Can I inject IServiceJAX to ServiceRS using annotation?
I don't want use JNDI lookup...
Sure, you can. Although I suppose there are other ways, I have successfully run a simple test project with a #Stateless #WebService, #Local implementation of an interface, injected through #EJB annotation into a #Stateless RESTFul web service annotated with #Path.
This is not properly a CDI injection as you have demanded, but it works nicely and probably fits your needs anyway.
IServiceJAX class:
public interface IServiceJAX {
public String hello(String txt);
}
IServiceJAXImpl class:
#WebService(serviceName = "NewWebService")
#Local
#Stateless
public class IServiceJAXImpl implements IServiceJAX {
#WebMethod(operationName = "hello")
#Override
public String hello(#WebParam(name = "name") String txt) {
return "Hello " + txt + " !";
}
}
ServiceRS class:
#Path("basic")
#Stateless
public class ServiceRS {
#EJB private IServiceJAX wsi;
#GET
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public String result(#PathParam("id") String id) {
return wsi.hello(id);
}
}
UPDATE
If you prefer CDI injection, you can keep the above code and simply remove #Local and #Stateless annotations from IServiceJAXImpl. You can inject an instance of this class using:
#Inject private IServiceJAX wsi;
instead of
#EJB private IServiceJAX wsi;