EJB:自定义身份验证和授权

EJB: custom authentication and authorisation

我正在尝试将自己的身份验证和授权添加到 Java EE REST 应用程序。我已经设法通过 JAX-RS 的 SecurityContextContainerRequestFilter 实现(使用 JWT)和端点方法上的 @RolesAllowed 注释获得了工作版本。但我需要 EJB,而它们根本不使用 JAX-RS 的 SecurityContext(无论用户角色如何,我都会得到 EJBAccessException),所以我需要另一个解决方案。

是否有类似 SecurityContext 的 EJB 可以实现?或者我应该使用像 Shiro 这样的库吗?我想从应用程序本身管理用户,因此容器或 LDAP 提供的用户管理不是一个选项。我正在使用 JPA 对用户进行身份验证和授权。

所以,主要问题是:

如何基于 JAX-RS 过滤器在 EJB 中实现我自己的身份验证和基于角色的授权机制(使用@RolesAllowed 注释)?我如何告诉 EJB请求与具有这些角色的具体经过身份验证的用户相关?

还有一件事 – 我宁愿避免特定于供应商的解决方案,但如果必须的话,我会选择 JBoss/Wildfly。

您当前的解决方案是否正确设置了 Principal 对象?它是 Java EE 安全性的核心,包括 EJB。

一般需要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,我个人认为这是一个有争议的决定。 KeyCloak 不提供应用程序内 IDM - 这是一项重要的功能,而这正是您正在寻找的。

JSR 375: Java™ EE Security API is an emerging specification that will address all the above in a standard, vendor-neutral way. Soteria 是一个 JSR 375 RI。目前,它只支持只读身份存储。

我会说您将在 REST Api 上使用 HTTP 身份验证 Header,这将由容器的配置进行验证。将有特定于供应商的实现,只是因为目前还没有 Java EE 供应商中立规范。验证用户后,将创建一个 Principal,并且所有 EJB @RolesAllowed 注释都将起作用。

我移植了 DukesForest to Wildfly,所以你可以看到它的一个例子。查看其余服务的 dukes-payment,注意 web.xml 和 jboss-web.xml。另外,查看数据库配置的 Entities 项目:

基本上,web.xml 将定义安全约束:

<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>

而 Wildfly 将需要添加一个 security-domain 来指定如何查询数据库:

<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>

这是基本思路。

PS> 还有一个Java Security Quickstart Archetype是在web框架中实现安全的,根据上面的例子应该很容易添加Http Basic Authentication

我已经为此工作了一段时间,并最终得出了我自己的解决方案。它是在 JAX-RS 接口级别实现的(如 Pradeep Pati 所建议的那样)——因此如果您需要通过 EJB 访问您的 bean,它将无法工作。

所以,正如我发现 hereContainerRequestFilter 可以访问资源方法(或 class)注释,所以我需要做的就是:

1.实现我自己的 @RolesAllowed 注解:

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

2。使用自定义身份验证和授权实施 ContainerRequestFilter

@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());
        }           
    }
    ...
}

但是,我也评估了Avishai的解决方案(使用PicketLink)。虽然这有点难以实现,有时可能会很复杂(例如,基本的 JPA 场景需要大约 7-8 个 JPA 实体),但如果您需要具有很多选项(如 LDAP 或JPA,甚至同时使用两者)或使用各种身份验证选项(例如同时使用 Basic 和 JWT 身份验证,但 headers 不同)。关于这个主题可能有数百个赞成 and/or 反对,所以这不是一个容易的选择。

有趣的是,PicketLink 使用它自己的 @org.picketlink.authorization.annotations.RolesAllowed 注释而不是 javax.annotation 注释。不过,它应该与 EJB 调用配合良好,因为它使用 EJB 拦截器,而不是 JAX-RS 过滤器来检查角色。

但对我来说,这似乎有点矫枉过正,所以我想出了自己的 not-so-sophisticated(但有效)解决方案。

我在 apache shiro 上构建了一个库来支持 java ee

中的身份验证和授权

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

你可以在测试文件夹中看到一个例子。

此致