Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAX-RS (Jersey 2) security, @PermitAll and @RolesAllowed not working as expected

I have a REST-API with three resources. The methods in the first one, called PublicResource, should be accessible for anyone (i.e. anonymous access). The methods in the second one, called SecretResource, should only be accessible for a specific group of users. Finally, the third resource, called MixedResource, has a mixed setup with some method being protected and some being open for public access.

The annotations @PermitAll and @RolesAllowed don't work as I'm expecting them to though. Although PublicResource is annotated with @PermitAll I'm still getting asked for authorization when trying to access it. The same goes for the methods in MixedResource that are annotated with @PermitAll. So basically, I'm getting asked for authorization everywhere in all of my resources even were I should have anonymous access.

I'm running on Payara 4.1 and I'm very confused since I had a very similar setup in another app running on WebLogic 12.1.3 and there the annotations worked as expected. What am I missing or getting wrong? See my full code below.

PublicResource.java:

    import javax.annotation.security.PermitAll;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.MediaType;

    @Path("public")
    @PermitAll
    public class PublicResource {

        @GET
        @Produces(MediaType.TEXT_PLAIN)
        public String itsPublic() {
            return "public";
        }

    }

SecretResource.java:

    import javax.annotation.security.RolesAllowed;
    import javax.ws.rs.GET;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;

    @Path("secret")
    @RolesAllowed({ "SECRET" })
    public class SecretResource {

        @GET
        @Produces(MediaType.TEXT_PLAIN)
        public String itsSecret() {
            return "secret";
        }

    }

MixedResource.java:

    import javax.annotation.security.PermitAll;
    import javax.annotation.security.RolesAllowed;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.MediaType;

    @Path("mixed")
    @PermitAll
    public class MixedResource {

        @GET
        @Path("public")
        @Produces(MediaType.TEXT_PLAIN)
        public String itsPublic() {
            return "public";
        }

        @GET
        @Path("secret")
        @RolesAllowed({ "SECRET" })
        @Produces(MediaType.TEXT_PLAIN)
        public String itsSecret() {
            return "secret";
        }

    }

JAXRSConfiguration.java:

    import javax.ws.rs.ApplicationPath;
    import javax.ws.rs.core.Application;

    @ApplicationPath("resources")
    public class JAXRSConfiguration extends Application {

    }

web.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
        <session-config>
            <session-timeout>30</session-timeout>
        </session-config>
        <security-constraint>
            <web-resource-collection>
                <web-resource-name>Basic Authorization</web-resource-name>
                <description/>
                <url-pattern>/resources/*</url-pattern>
            </web-resource-collection>
            <auth-constraint>
                <role-name>SECRET</role-name>
            </auth-constraint>
        </security-constraint>
        <login-config>
            <auth-method>BASIC</auth-method>
            <realm-name>file</realm-name>
        </login-config>
        <error-page>
            <exception-type>java.lang.Throwable</exception-type>
            <location>/error/internal</location>
        </error-page>
        <security-role>
            <role-name>SECRET</role-name>
        </security-role>
    </web-app>

glassfish-web.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
    <glassfish-web-app error-url="">
        <class-loader delegate="true"/>
        <security-role-mapping>
            <role-name>SECRET</role-name>
            <group-name>cia</group-name>
        </security-role-mapping>
        <jsp-config>
            <property name="keepgenerated" value="true">
                <description>Keep a copy of the generated servlet class' java code.</description>
            </property>
        </jsp-config>
    </glassfish-web-app>

beans.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
   bean-discovery-mode="all">
    </beans>
like image 277
FighterHayabusa Avatar asked Apr 13 '17 11:04

FighterHayabusa


1 Answers

At the face of it, this is not a problem with Jersey. The only real security you have configured is at the servlet container level. None of your Jersey security annotations have any effect, as you haven't even configured the Jersey specific support for it.

First look at you web.xml configuration

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Basic Authorization</web-resource-name>
        <description/>
        <url-pattern>/resources/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>SECRET</role-name>
    </auth-constraint>
</security-constraint>

The first part sets up that every resource requires authentication. So what you think is a problem with @PermitAll not working, is actually caused by the fact that you configured servlet to not let anyone through that is not authenticated.

Then you set up the authorization at the servlet container level to only allow users with the SECRET role. So all your security configuration is configured here in the web.xml. Nothing to do with Jersey.

The other thing you need to understand to fix this problem, is the difference between authentication and authorization, and who plays a role in what (with regards to the servlet container and Jersey).

The @PermitAll, @RolesAllowed, etc are only ways of Jersey handling authorization. It is expected that there already be an authenticated user. How Jersey checks this is by getting the principal from the servlet request. If there is no principal, then it is assumed that there is not authenticated user, and then will not allow anyone through, even if you have @PermitAll, as even @PermitAll requires a user to be authenticated.

That being said, I don't know is there's a way to set an anonymous user in your server. This is what would be required to get the behavior you want. Remember that Jersey still requires that there be an authenticated user. So unless you can somehow set an anonymous user at the container, I don't see you could make this work.

If you had different paths though for secured and not secured, then you could just configure the secured paths in the web.xml, and everything else just let go. With this, there wouldn't even be a need for @PermitAll. Remember, with @PermitAll, it requires an authenticated user. But if you just remove the the @PermitAll, then all requests would just go through. This would be possible if you separated the paths between secured and not secured.

If you want more control over all this, you might be better off implementing your own security instead of using the servlet container's. Normally I wouldn't recommend this, but Basic Authentication is probably the easiest security protocol to implement. You can check out this example, where everything is done using Jersey filters.

The last thing to note is the fact that you haven't even configured the authorization support for Jersey. You still need to register the RolesAllowedDynamicFeature with the application. Since you are using classpath scanning (empty Application class) for your JAX-RS configuration, then you will need to register it from a Feature, as mentioned in this post

like image 153
Paul Samsotha Avatar answered Oct 18 '22 09:10

Paul Samsotha