使用拦截器和注入在 JAX-RS 中进行身份验证/授权

Posted

技术标签:

【中文标题】使用拦截器和注入在 JAX-RS 中进行身份验证/授权【英文标题】:Authentication/authorization in JAX-RS using interceptors and injection 【发布时间】:2014-05-26 22:48:39 【问题描述】:

我正在使用 WildFly 8 在 JavaEE 7 中开发一个新应用程序。我正在使用 JAX-RS 为远程应用程序提供 RESTful 服务接口。

可以使用@Context 注解将HttpHeaders 对象之类的东西注入到资源方法参数中。由于该对象基于请求参数(当然是 HTTP 标头),我想出了创建自己的可注入 User 对象的想法,该对象是基于请求中存在的有效令牌(类似于OAuth 访问令牌)。

所以,我想实现这样的目标:

@Path("/resources")
public class MyResource 

    @Path("/id")
    @GET
    public Response getById(@Context User user, @PathParam("id") long id) 
        ...
    


其中 User 是根据请求参数创建的可注入对象,例如可通过 HttpHeaders 对象访问的对象。当然,如果由于任何原因无法创建 User 对象,提供者也可以抛出异常并返回 HTTP 错误响应。

现在,我的问题是:

    这是一个好的设计吗?如果没有,我还有什么更好的选择? 我怎样才能做到这一点?我不在乎我的解决方案是否特定于 JAX-RS 并使用特定于 WildFly/RestEasy 的内部结构,但如果存在便携式解决方案,我绝对更喜欢便携式解决方案。

谢谢

【问题讨论】:

【参考方案1】:

在我看来,只要您不尝试使用此用户对象构建类似会话的东西,这种方法就有效。

作为answered here,您可以使用@Context 和@Provider,但这并不是您想要的。 使用Resteasy Dispatcher 可以根据@Context 直接注入一个类。 但是在这里你必须注册应该注入的对象。我认为这对于请求范围的参数没有意义。 你可以做的是注入这样的提供者:

// Constructor of your JAX-RS Application
public RestApplication(@Context Dispatcher dispatcher) 
    dispatcher.getDefaultContextObjects().put(UserProvider.class, new UserProvider());


// a resource
public Response getById(@Context UserProvider userProvider) 
    User user = userProvider.get();

解决问题的其他方法:

    注册一个WebFilter,验证用户,包装ServletRequest并覆盖getUserPrincipal。然后,您可以从注入的 HttpServletRequest 访问 UserPrincipal。 实现一个 JAX-RS 拦截器,它实现了ContainerRequestFilter。将 ContainerRequestContext.html#setSecurityContext 与 UserPrincipal 一起使用,并将 SecurityContext 作为 ResourceMethod-Parameter 注入。 实现一个CDI-Interceptor,它会更新您的方法参数。 实现一个类 which produces 您的用户并通过 CDI 注入它。

我将示例推送到github。

【讨论】:

【参考方案2】:

虽然 lefloh 的回答绝对正确,但我想详细说明他认为最方便的第二种方法。

您将创建实现ContainerRequestFilter 的拦截器(过滤器)。

import org.apache.commons.lang.StringUtils;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
public class UserProvider implements ContainerRequestFilter 

    @Override
    public void filter(ContainerRequestContext containerRequestContext) throws IOException 
        String userId = containerRequestContext.getHeaders().getFirst("User-Id");
        if (StringUtils.isEmpty(userId)) 
            Response response = Response
                    .status(Response.Status.BAD_REQUEST)
                    .type(MediaType.TEXT_PLAIN_TYPE)
                    .entity("User-Id header is missing.")
                    .build();
            containerRequestContext.abortWith(response);
            return;
        

        //do your logic to obtain the User object by userId

        ResteasyProviderFactory.pushContext(User.class, user);
    

您需要通过自动发现 set up in web.xml 来注册它

<context-param>
    <param-name>resteasy.scan</param-name>
    <param-value>true</param-value>
</context-param>

或在您的应用启动中

ResteasyProviderFactory.getInstance().registerProvider(UserProvider.class);

由于获取用户背后的所有逻辑都在 UserProvider 中,因此您可以像这样使用它

@Path("/orders")
@GET
public List<Order> getOrders(@Context User user) 
    return user.getOrders();

【讨论】:

非常感谢。你有什么想法可以跨 JEE 容器移植吗? resteasy.scan 已完全弃用。它只是被忽略了。

以上是关于使用拦截器和注入在 JAX-RS 中进行身份验证/授权的主要内容,如果未能解决你的问题,请参考以下文章

JAX-RS 安全性和身份验证

使用 JAX-RS Jersey 进行身份验证和授权的简便方法

使用 JAX-RS Jersey 进行身份验证和授权的简便方法

Java:使用带有 Spring Security 的 Jersey Jax-RS 进行用户身份验证

JAX-RS 和 Spring Security - 获取经过身份验证的用户

jax-rs RESTful 服务的 spring 安全配置