Jakub Wojnowski
Jakub Wojnowski

Reputation: 138

EJB: custom authentication and authorisation

I’m trying to add my own authentication and authorisation to Java EE REST application. I’ve managed to get working version with JAX-RS’s SecurityContext, ContainerRequestFilter implementation (with JWT) and @RolesAllowed annotations on end-point methods. But I need EJBs, and they don’t use JAX-RS’s SecurityContext at all (I’m getting EJBAccessException regardless of user roles), so I need another solution.

Is there anything like SecurityContext in EJBs possible to implement? Or should I use a library like Shiro? I want to manage users from the application itself, so container- or LDAP-provided user management is not an option. I'm using JPA to authenticate and authorise a user.

So, the main question is:

How do I implement my own authentication and role-based authorisation mechanisms working in EJBs (using @RolesAllowed annotations), based on JAX-RS filters? How do I tell EJB that a request is related to that concrete authenticated user with these roles?

One more thing – I’d rather avoid vendor-specific solutions, but if I had to, I’d go with JBoss/Wildfly.

Upvotes: 2

Views: 2730

Answers (4)

Panchitoboy
Panchitoboy

Reputation: 830

I made a library build on apache shiro to support authentication and authorisation in java ee

https://github.com/panchitoboy/shiro-jwt

You can see a example in the test folder.

Regards

Upvotes: 0

Jakub Wojnowski
Jakub Wojnowski

Reputation: 138

I’ve been working on this for a while and ended up with my own solution. It is implemented at JAX-RS interface level (as suggested by Pradeep Pati) – so if you need to access your beans by EJB, it won’t work.

So, as I've found here, ContainerRequestFilter can access the resource method (or class) annotations, so all I needed to do is:

1. Implement my own @RolesAllowed annotation:

@Inherited
@Target( {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RolesAllowed {
    String[] value();
}

2. Implement ContainerRequestFilter with custom authentication and authorization:

@Provider
@Priority(Priorities.AUTHENTICATION)
public class SecurityFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext context) throws IOException {
        // here we have access to headers:
        String authorizationHeader = context.getHeaderString("Authorization");

        // and, thanks to injected resourceInfo, to annotations:
        RolesAllowed annotation = resourceInfo
                .getResourceClass() // or getResourceMethod(), I've used both
                    .getAnnotation(RolesAllowed.class);
        // and, finally, to the roles (after a null-check)
        String[] roles = annotation.value();

        // then you can authenticate and authorize everything on your own using any method (I’ve used Basic Auth and JWT)
        // and, if something fails, you can abort the request:
        if (!isAuthenticated) {
            context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        } else if (!isAuthorized) {
            context.abortWith(Response.status(Response.Status.FORBIDDEN).build());
        }           
    }
    ...
}

However, I’ve evaluated Avishai’s solution (to use PicketLink) too. While this is a little harder to implement and sometimes could be complicated (for example basic JPA scenario needs about 7-8 JPA entities), it would be better choice if you need solid, extensible security system with a lot of options (like LDAP or JPA, or even both at the same time) or with various authentication options (like Basic and JWT auth at the same time, but with different headers, for example). There are probably hundreds of pros and/or cons on the topic, so that's not an easy choice.

Funny thing is, PicketLink uses it’s own @org.picketlink.authorization.annotations.RolesAllowed annotation instead of javax.annotation one. Still, it should work well with EJB calls because it uses EJB interceptors, not JAX-RS filters to check the roles.

But for me it had seemed like an overkill, so I came up with my own, not-so-sophisticated (but working) solution.

Upvotes: 1

K.Nicholas
K.Nicholas

Reputation: 11551

I'd say that you would use a HTTP Authentication Header on the REST Api, and this would be validated by the Container's configuration. There will be a certain vender specific implementation just because there isn't a Java EE vender neutral specification on this yet. After the user is validated, then a Principal is created and all the EJB @RolesAllowed annotations will function.

I ported DukesForest to Wildfly, so you can see an example of it in action. Look at dukes-payment for the rest service, note the web.xml and the jboss-web.xml. Also, look at the Entities project for the database configuration:

Basically, the web.xml will define the security constraint:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Secure payment service</web-resource-name>
        <description/>
        <url-pattern>/*</url-pattern>
        <http-method-omission>GET</http-method-omission>
    </web-resource-collection>
    <auth-constraint>
        <role-name>USERS</role-name>
    </auth-constraint>
</security-constraint>
<login-config>
    <auth-method>BASIC</auth-method>
</login-config>
<security-role>
    <role-name>USERS</role-name>
</security-role>

And Wildfly will need a security-domain added to specify how to query the database:

<security-domain name="dukes-forest" cache-type="default">
    <authentication>
        <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
            <module-option name="dsJndiName" value="java:jboss/ForestXADS"/>
            <module-option name="rolesQuery" value="select NAME as 'ROLES', 'Roles' as 'ROLEGROUP' from forest.GROUPS g inner join forest.PERSON_GROUPS pg on g.ID = pg.GROUPS_ID join forest.PERSON p on p.EMAIL = pg.EMAIL where p.EMAIL = ?"/>
            <module-option name="hashAlgorithm" value="MD5"/>
            <module-option name="hashEncoding" value="HEX"/>
            <module-option name="principalsQuery" value="select PASSWORD from forest.PERSON where EMAIL=?"/>
        </login-module>
    </authentication>
    <authorization>
        <policy-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
            <module-option name="dsJndiName" value="java:jboss/ForestXADS"/>
            <module-option name="rolesQuery" value="select NAME as 'ROLE', 'ROLES' as 'ROLEGROUP' from forest.GROUPS g inner join forest.PERSON_GROUPS pg on g.ID = pg.GROUPS_ID join forest.PERSON p on p.EMAIL = pg.EMAIL where p.EMAIL = ?"/>
            <module-option name="hashAlgorithm" value="MD5"/>
            <module-option name="hashEncoding" value="HEX"/>
            <module-option name="principalsQuery" value="select PASSWORD from forest.PERSON where EMAIL=?"/>
        </policy-module>
    </authorization>
</security-domain>

This is the basic idea.

PS> There is also a Java Security Quickstart Archetype that implements security in a web framework, and it should be easy to add Http Basic Authentication based on the examples above.

Upvotes: 1

Dimitri
Dimitri

Reputation: 301

Does your current solution set up Principal object correctly? It is central to Java EE security, including EJBs.

Generally, you need an auth + IDM solution with support for JPA and custom authentication methods; PicketLink could be your choice. Unfortunately, PicketLink is now said to be superseded by KeyCloak, which I personally consider to have been a controversial decision. KeyCloak doesn't provide in-application IDM - it's an important piece of functionality, and it's exactly what you're looking for.

JSR 375: Java™ EE Security API is an emerging specification that will address all the above in a standard, vendor-neutral way. Soteria is a JSR 375 RI. At the moment, it only supports read-only identity stores.

Upvotes: 2

Related Questions