Learn to use RESTEasy ContainerRequestFilter to create security filter which is able to to perform authentication and authorization on RESTEasy based web applications.
1. RESTEasy ContainerRequestFilter and ContainerReponseFilter
The new RESTEasy version 3.0.2 final has been recently released and is made compatible with JAX-RS 2.0. If you remember that previous JAX-RS releases had no specification regarding implementing filters and interceptors. That’s why all JAX-RS implementations had their own flavors. RESTEasy had PreProcessorInterceptor and PostProcessorInterceptor which are deprecated now.
Now JAX-RS has it’s own specification around filters and interceptors. You can read a detailed discussion by this post by Bill Burke.
In resteasy, Filters are run before and after the resource method is invoked. These filters are essentially, ContainerRequestFilter and ContainerReponseFilter. ContainerRequestFilters run before your JAX-RS resource method is invoked. ContainerResponseFilters run after your JAX-RS resource method is invoked. As an added caveat, ContainerRequestFilters come in two flavors: pre-match and post-matching. Pre-matching ContainerRequestFilters are designated with the @PreMatching annotation and will execute before the JAX-RS resource method is matched with the incoming HTTP request. Post matching ContainerRequestFilters execute after the Java resource method has been matched.
While filters modify request or response headers, interceptors deal with message bodies. They can be used to implement a specific content-encoding. They can be used to generate digital signatures or to post or pre-process a Java object model before or after it is marshalled.
2. RESTEasy ContainerRequestFilter Example
In this post, I am modifying the resteasy authentication and authorization tutorial which was originally written in RESTEasy “2.3.1.GA” using PreProcessorInterceptor. I have updated it to RESTEasy version “3.0.2.Final” which is build on JAX-RS 2.0 specification.
2.1. Update maven dependencies
As I am using maven, I have updated the pom.xml file as below. If you are using ant or jar file, then update the required jars accordingly.
<dependencies> <!-- core library --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>3.0.2.Final</version> </dependency> <!-- JAXB support --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> <version>3.0.2.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>jaxrs-api</artifactId> <version>3.0.2.Final</version> </dependency> <dependency> <groupId>net.sf.scannotation</groupId> <artifactId>scannotation</artifactId> <version>1.0.3</version> </dependency> </dependencies>
2.2. RESTEasy SecurityInterceptor
As JAX-RS 2.0 has filters for pre and post request handling, we will be using ContainerRequestFilter
interface. Remember PreProcessorInterceptor is deprecated now.
@Provider public class SecurityInterceptor implements javax.ws.rs.container.ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) { //More code... } }
Now, first we have to access the resource method to check the security constraints and attributes it define.
ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) requestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker"); Method method = methodInvoker.getMethod();
Now we have the access to resource method. Now everything will be same as we were doing it previously. i.e.
- Check PermitAll annotation, if it is present then no need to check anything further
- Check DenyAll annotation, if it is present then return with access-denied
- Check RolesAllowed annotation, and fetch the roles required from annotation. Get authorization information from request and match it as per application logic. If authorization is successful, give the access otherwise return access-denied.
2.3. RESTEasy SecurityInterceptor sourcecode
Complete code for SecurityInterceptor is as follow.
package com.howtodoinjava.demo.rest.security; import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.Provider; import org.jboss.resteasy.core.Headers; import org.jboss.resteasy.core.ResourceMethodInvoker; import org.jboss.resteasy.core.ServerResponse; import org.jboss.resteasy.util.Base64; /** * This interceptor verify the access permissions for a user * based on username and passowrd provided in request * */ @Provider public class SecurityInterceptor implements javax.ws.rs.container.ContainerRequestFilter { private static final String AUTHORIZATION_PROPERTY = "Authorization"; private static final String AUTHENTICATION_SCHEME = "Basic"; private static final ServerResponse ACCESS_DENIED = new ServerResponse("Access denied for this resource", 401, new Headers<Object>());; private static final ServerResponse ACCESS_FORBIDDEN = new ServerResponse("Nobody can access this resource", 403, new Headers<Object>());; private static final ServerResponse SERVER_ERROR = new ServerResponse("INTERNAL SERVER ERROR", 500, new Headers<Object>());; @Override public void filter(ContainerRequestContext requestContext) { ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) requestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker"); Method method = methodInvoker.getMethod(); //Access allowed for all if( ! method.isAnnotationPresent(PermitAll.class)) { //Access denied for all if(method.isAnnotationPresent(DenyAll.class)) { requestContext.abortWith(ACCESS_FORBIDDEN); return; } //Get request headers final MultivaluedMap<String, String> headers = requestContext.getHeaders(); //Fetch authorization header final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY); //If no authorization information present; block access if(authorization == null || authorization.isEmpty()) { requestContext.abortWith(ACCESS_DENIED); return; } //Get encoded username and password final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", ""); //Decode username and password String usernameAndPassword = null; try { usernameAndPassword = new String(Base64.decode(encodedUserPassword)); } catch (IOException e) { requestContext.abortWith(SERVER_ERROR); return; } //Split username and password tokens final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":"); final String username = tokenizer.nextToken(); final String password = tokenizer.nextToken(); //Verifying Username and password System.out.println(username); System.out.println(password); //Verify user access if(method.isAnnotationPresent(RolesAllowed.class)) { RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class); Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value())); //Is user valid? if( ! isUserAllowed(username, password, rolesSet)) { requestContext.abortWith(ACCESS_DENIED); return; } } } } private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet) { boolean isAllowed = false; //Step 1. Fetch password from database and match with password in argument //If both match then get the defined role for user from database and continue; else return isAllowed [false] //Access the database and do this part yourself //String userRole = userMgr.getUserRole(username); String userRole = "ADMIN"; //Step 2. Verify user role if(rolesSet.contains(userRole)) { isAllowed = true; } return isAllowed; } }
2.4. RESTEasy security filter demo
To test the security code, deploy the web application in any application server like Tomcat. Now, send following requests:
- HTTP GET
http://localhost:8080/RESTEasyEtagDemo/user-service/users/1
without username and passwordUser is able to access the API successfully.
- HTTP PUT
http://localhost:8080/RESTEasyEtagDemo/user-service/users/1
without username and passwordUser is not able to access the API.
- Add basic authorization credentials
- HTTP PUT
http://localhost:8080/RESTEasyEtagDemo/user-service/users/1
with username and password addedUser is able to access protected API
That’s all in resteasy security interceptor example. If you have any query or suggestion, drop me a comment.
Update: Below are the steps to run this project in tomcat 7.
Today, I again worked on this project to run on tomcat 7. To run successfully, I did following steps:
– Import the project in eclipse
– Run Prompt > mvn eclipse:eclipse -Dwtpversion=2.0 in project root folder [Reference]
– Update the @Produces and @Consumes annotations on methods
– Start the tomcat server and test the application. You will get desired results.
Happy Learning !!
Hello, now I want to write an example about Jersey permission. Can you help me? I can’t download your example. Can you send it to my mailbox?
E-mail: 210054368@qq.com
Thanks very much
for Tomcat 9.08 , servlet api 4 , java 1.8
web.xml
pom.xml
Create an annotation
Put the annotation on the SecurityInterceptor, and resource methods
Thanks for sharing !!
example not running in WildFly10:
Do I have to provide User and Password for every request? is there any solution for session token? is it any problem versus session token? is this way better ? how do I provide the user and password like in your example?
Thanks for your help
when i run this application SecurityInterceptor class is not called . After some change this class invoked inceptor called but
on this line methodInvoker.getMethod(); throw nullpointer exception.
Can you please help me
Any idea how can I get a full path of request (or have access to HttpRequest or ResourceMethod) without reflection hacks?
Hi,
My Interceptor implementation is not being invoked. I have done a similar implementation.
~John
Hi,
grate job, thanks.
i used jersey and spring integration.
When i called the annotated method without header i obtained the error message. but if i recall it for the new time i have a response with Status(200) and empty body !!!
Do you have an idea ?
Thanks
@Autowired for Dao class is not working in filter implementation class. Everything else is working fine. I am using Dao class to get user information & roles from DB. So if i try to @Autowired that Dao in @Provider filter class, it is returning null. Can you help me?
Use this information: http://docs.jboss.org/resteasy/ /docs/2.2.1.GA/ userguide/html/ RESTEasy_Spring_Integration.html
Yes i have implemented the same what is there in the above. I got DAO injected in my Resource class. I am seeing issue only in Filter implementation class.
Thanks,
Mohan
throws below exception. Any clue?
Above not returning the Customized Response Message but throws exception message.
I saw like in the above tutorial we are passing userid/password through browser for calling secure restfull webservices. Am trying to access secure restfull using java client. Could you please give me an idea like how we can pass userid/password through java client to the restfull service to make a connection?
Is it OK? Or your requirement is different?
What browser do you use? I have never seen the browser from your snapshot.
It’s firefox plugin RESTClient for testing RESTful webservices.
I saw like in the above tutorial we are passing userid/password through browser for calling secure restfull webservices. Am trying to access secure restfull using java client. Could you please give me an idea like how we can pass userid/password through java client to the restfull service to make a connection?
I will write one client code this week.
Hi i have the same question, Am trying to access secure restfull using java client. Could you please give me an idea like how we can pass userid/password through java client to the restfull service to make a connection?
In my app I need to know a little more about the user. If I implement a ContainerRequestFilter as you’ve suggested above and populate a User object from the database, how do I pass that object to my resource?
My User object contains details about where the user’s account is located, their configuration options, account status, and lots of other things I need to know to service the request. I’d rather not have to hit the database a second time within the /resource to get that information.
You can use threadlocal. https://howtodoinjava.com/resteasy/share-context-data-with-jax-rs-resteasyproviderfactory/
Hi Lokesh,
This is really a great example. Thanks for providing such a useful code here. I am also trying to implement restful webservice with security signing and encryption so as to add an extra layer of security on top of what SSL/https provides. Could you please provide me a n example using RESTEasy as I dont find it so easy with the documentation available for RESTEasy. I have done the same using Spring WS security and trying the same with REST . Unfortunately I am completely blocked.. Can you please help me go forward. Thanks in advance.
Regards,
Satya
Hi, i’ve downladed your example, but this doesn’t work for me, maybe yoy did a extra config? please help me.
ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) requestContext.getProperty(“org.jboss.resteasy.core.ResourceMethodInvoker”); returned null for methodInvoker, do u have any idea about this?
Hi,
great and clear example.
I’ve followed it , creating one get method with @PermitAll and another get method with @RolesAllowed(“ADMIN”).
get method with @permitAll works properly, while method with @RolesAllowed(“ADMIN”) doesn’t work and this exception has been raised :
Caused by: javax.ejb.EJBAccessException: JBAS014502: Invocation on method: public java.lang.String …..) throws javax.naming.NamingException of bean: NfvAgWS is not allowed
I’ve debugged it and it’s strange but in this second case all check in filter method are passed (no requestContext.abortWith are invoked).
Can I debug check something in order to find the problem?
thanks in advance
l
any suggestion?
can I debug my problem maybe implementing another class in order to itercept something that happen after SecurityInterceptor.filter(ContainerRequestContext requestContext)?
thanks in advance
l
Hi luke, it’s really very hard for me to suggest you anything for this exception. I have also seen this exception first time. If possible, can you please post the complete (at least some more) error log here, that will be more helpful.
I’m deployed your example in WildflyAS, when I invoke rest method annotated with @RolesAllowed(“ADMIN”), following exception has been raised.
I’ve debugged it, and I’ve verified that all controls in filter method pass correctly and requestContext.abortWith(…) are never invoked.
I don’t know what other I can control.
thanks in advance
luke
P.S.:
Exception Raised during get operation:
Can you please try this solution. Set it to false.
in jboss.xml.
Hi in wildfly I think I’ve to modify jboss-ejb3.xml, as suggested here:
https://docs.jboss.org/author/display/WFLY8/Securing+EJBs
but at the moment I’m trying to understand exactly xml schema, cause in post there only an xml fragment
Hi tried to add a WEB-INF/jboss-ejb3.xml file like this
MyEJBName
false
but it seems not working..I’ve again previous exception
thanks?
any idea? what I’m doing wrong?
Wrap the code inside [java-xml] tags as suggested in below note in blue.
I’m sorry:
Aniway I think that missing-method-permissions-deny-access permit only to enable rest operation that aren’t annotated.
I’ve another problem I can’t access rest operation annotated with
@RolesAllowed(“ADMIN”)
I think the problem is that I’ve to configure in my AS( wildfly) a role named “ADMIN”….and I don’t know how to do that..
thanks for your help
Me also not aware of this. You are at your own for now.
Hello Luke,
I am running into the same problem. Have you found a solution?
Hi Lokesh,
I downloaded your source code example and followed the step you listed, the compilation was success. The problems is I don’t see any war being generated.. how do I deploy this example in tomcat?
You can import project into eclipse and deploy from there in tomcat. Eclipse will generate the war file as well if you want to deploy separately. Third option is to create a project in your preferred IDE, copy all sourcecode files and then crate a war file from it.
Hey there, great tutorial,
I have a question though, while testing the first (GET) query i get the following response:
Could not find MessageBodyWriter for response object of type: User of media type: text/html
I also tried changing content type of the query to application/xhtml+xml and i get:
Could not find MessageBodyWriter for response object of type: User of media type: application/octet-stream
Any clue what might be wrong?
(Im using wildfly with resteasy 3.0.7 + resteasy-jaxb-provider-3.0.7)
Thanks!
Probably you are not setting the request header “accept” as “application/xhtml+xml”. And make sure you have dependency of “resteasy-jaxb-provider.jar”.
Hi there, i set it to @Produces(MediaType.APPLICATION_JSON) and works like a charm!
Thanks again!
Is it possible to get a working example for Glassfish, please? I keep getting the following exception when I deploy the project: javax.ejb.AccessLocalException: Client not authorized for this invocation. The functions with the @PermitAll annotation work as expected. Thank you in advance.
Never mind, I had to use a completely different approach. For those with the same problem, you can find the solution here:
https://stackoverflow.com/questions/7944963/glassfish-3-security-form-based-authentication-using-a-jdbc-realm
my project never call SecurityInterceptor class and it’s to big to send to you.. How can I fix this?
Make sure you have added both annotations on top of class. i.e. @Provider and @ServerInterceptor. Also, resteasy scan is set to true in web.xml.
I got this error now:
Caused by: java.lang.NoClassDefFoundError: javax/ws/rs/container/ContainerRequestFilter
NoClassDefFoundError in Java comes when Java Virtual Machine is not able to find a particular class at runtime which was available during compile time.
Which server (and version) you are using? Provide me more details about your configuration.
HiPavan
I tried your code on tomcat but filter method of SecurityInterceptor is not getting invoked any pointers?
Updated the post.
Hi, when downloaded your project and try debug it, i can goto SecurityInterceptor class.
But when i’m created new web service project anh using SecurityInterceptor, my project never call SecurityInterceptor class.
How to fix it?
Send me your code. howtodoinjava@gmail.com
Did you solve this issue? I’m trying your solution as well but the Interceptor’s filter method is not called. I’m using jboss 7 with resteasy 3.0.5. The @DenyAll annotation is not taken into account at all.
Should I put some and/or tags in my web.xml file?
Thanks for the nice tutorial.
Above user didn’t shared the code. Are you able to hit interceptor with “RolesAllowed”?
No, it doesn’t work either way. However, I looked into the logs and it seems that my jboss uses JAX-RS 1.1 and since 2.0 is not yet supported, even though I include the RESTEasy 3.0.5. I used your previous example, with the PreProcessor and it works fine. I guess I’ll stick with it for now.
Please download the sourcecode of this post and try to identify the gaps. I assure you that it is working perfect.
Excellent tutorial…about the only real how to out there! I’ve just converted to this final 3.0.2 version and the filter method is not being called/ContainerRequestFilter is not being invoked. Is there some secret in web.xml that I’m missing? I’m unable to access Google Docs here at work (firewall rules) so I can’t see your complete project. On the previous version, I did have to add to my web.xml with resteasy.scan as name and true as value…but this is not the case here, as far as I can tell. Any assistance would be greatly appreciated!
Hey Mike, I am on vacation for next 4-5 days. I have very limited connectivity here and no-connectivity to dev tools. I will try to assist you once back to my home.
I can’t go any deeper with the comments so I’ll reply here. First of all, your project doesn’t import correctly in my setup. I get the following error:
An internal error occurred during: “Importing Maven projects”.
Unsupported IClasspathEntry kind=4
And then none of the annotations are legal and most of the dependencies are somehow invalid.
Like I said, I believe my jboss still uses JAX-RS 1.1 even if I add the 3.0.5 RESTeasy dependency. I get it from this message I get when deploying my server:
JBAS011204: resteasy.scan found and ignored in web.xml. This is not necessary, as Resteasy will use the container integration in the JAX-RS 1.1 specification in section 2.3.2
I read somewhere that RESTeasy distribution that comes with jboss is deeply connected with the distribution and it’s not that easy to change it without modifying the jboss config files, i.e. standalone.xml. Also, look here – https://issues.jboss.org/browse/JBEE-131
Fair enough. Yes its not easy to change resteasy version with jboss. Regarding project does not import correctly in your setup, It is your machine specific issue and I may not be able to help you without correct understanding. So you are upto your own.
Regarding annotations validity error, They are due to project import errors.
Can you please be specific what you want to achieve, I will try to solve your issue when I am back on work. [Currently on holidays]
I know I probably could solve the import issue, although I did it on two computers with the same result 😉 I understand and follow your code but the current jboss setup simply doesn’t allow me to use it and I’m not in the position now to change it. I based my solution on your previous tutorial (using PreProcessInterceptor) and so far I’m satisfied with the authentication/authorization control I have. So thanks again for the help!
Welcome
I found the solution to the RESTeasy version conflict. All you need to do is download the latest RESTeasy zip file and then do what the documentation says:
3.1. Upgrading Resteasy Within JBoss AS 7
Resteasy is bundled with JBoss AS 7. You will likely have the need to upgrade Resteasy in AS7. The Resteasy distribution comes with a zip file called resteasy-jboss-modules-3.0.5.Final.zip. Unzip this file while with the modules/ directory of the JBoss AS7 distribution. This will overwrite some of the existing files there.
figured it out, the solution:
Method method = ((ExtendedUriInfo) requestContext.getUriInfo()). getMatchedResourceMethod(). getInvocable(). getHandlingMethod();
Glad, you made it.
Filly, thanks for posting this fix. i know this is a few months old but have you got this code to work for ajax calls? It works great for RESTClient but returns a generic empty wadl resource method when the request comes from ajax. Any ideas?
i get a nullpointerException in following line : Method method = methodInvoker.getMethod();
because the only property in ContainerRequestContext is “org.glassfish.jersey.message.internal.TracingLogger”. but NO “org.jboss.resteasy.core.ResourceMethodInvoker”
what am i doing wrong ?
Github repository ???
I prefer google docs.