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>
Related
I created an interceptor that intercepts the call to my method and also its response. What happens is that I have to use a service while intercepting and the service works fine when intercepted on the request, but is null when intercepted on the response.
I tried separating the interceptor so the implementation of WriterInterceptor was in another class, but it seems to behavior the same.
#POST
#Path("/test")
#MyAnnotation
public void test(
final #Suspended AsyncResponse response,
final #ApiParam(value = "Test.", required = true) #FormParam("test") String test)
throws ServiceException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new AsyncResponseCallable(response) {
#Override
public Response execute() throws Exception {
Object object = service.foo();
Status status = Status.OK;
return Response.status(status).entity(object).build();
}
});
}
#Provider
#MyAnnotation
public class MyInterceptor implements WriterInterceptor {
#EJB
private MyService myService;
#AroundInvoke
public Object interceptor(InvocationContext context) throws Exception {
myService.test();
return context.proceed();
}
#Override
public void aroundWriteTo(WriterInterceptorContext context) throws WebApplicationException {
myService.test();
context.proceed();
}
}
#Stateless
public class MyService {
public void test() {
System.out.println("Done");
}
}
The method aroundWriteTo should have an instance of myService injected by the EJB annotation, but it is null.
Can you guys help me figure this out?
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 wanted to use a interceptor(custom annotation) inside a jax-rs service.
1.First,I wrote an annotation class:
BasicAuthentication.java:
#NameBinding
#Target( {ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface BasicAuthentication {
}
2.Then I added the BasicAuthenticationInterceptor implements javax.ws.rs.ext.ReaderInterceptor
BasicAuthenticationInterceptor.java:
#Provider
#Priority(Priorities.AUTHENTICATION)
#BasicAuthentication
public class BasicAuthenticationInterceptor extends Dumpable implements ReaderInterceptor {
#Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
//log.info("authentication here")
String authHeader = context.getHeaders().getFirst(AUTHORIZATION);
if (authHeader == null) {
error("\"authorization\" is not found from the request header.");
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
return context.proceed();
}
}
3.At last,I add a test service with annotation #BasicAuthentication.
TestRestfulService.java
#Stateless
#Path("/api")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
#BasicAuthentication
public class TestRestfulService extends Dumpable{
#EJB
LocalService localService;
#Path("/test/{id}")
#GET
public Response test(#PathParam("id")String id) {
try {
localService.findUser(id);
} catch (Exception e) {
error(e);
return Response.serverError().build();
}
return Response.ok().build();
}
}
But every time I request /api/test/1 with empty header,I can get the correct response,the interceptor seems not work at all.
I'm using Wildfly 10.
Thanks in advance.
Finally I worked out.Change the Interceptor to filter:
public class BasicAuthenticationInterceptor implements javax.ws.rs.container.ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context){
...
}
}
Then it works as expected.
Form JAX-WS API:
Interface for message body reader interceptors that wrap around calls to MessageBodyReader.readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.InputStream).
javax.ws.rs.ReaderInterceptor
This could be the reason.
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);
}
}
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();
}
}