如何在注入 JAX-RS Web 服务的 CDI bean 中获取 HTTP 请求标头?

Posted

技术标签:

【中文标题】如何在注入 JAX-RS Web 服务的 CDI bean 中获取 HTTP 请求标头?【英文标题】:How do I get hold of HTTP request headers in a CDI bean that's injected into a JAX-RS webservice? 【发布时间】:2016-01-04 16:22:36 【问题描述】:

我有这样的网络服务:

@Path("/projects")
public class Projects 
    [...]

    @Inject
    CurrentRequest current;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("id")
    public Response getProject(@PathParam("id") String id) 
        if (current.isUserAuthenticated()) 
            [...do something...]
         else 
            [...produce an error...]
        
    

还有一个 CDI bean,它带有这样的身份验证检查器方法:

@RequestScoped
public class CurrentRequest 

    public boolean isUserAuthenticated() 
        [...do some header checking...]
    

我的问题是我一辈子都无法从CurrentRequest 内部获取 HTTP 标头。我尝试注入HttpServletRequest,但它没有初始化。我尝试使用@Context,同样的事情。显然FacesContext.getCurrentInstance() 也不起作用,因为没有 FacesContext。

我看到this question基本上是在问同样的事情,但没有受到太多关注。

我目前的方法是在Projects 中使用@Context HttpServletRequest request,并将其作为参数传递给current.isUserAuthenticated(request)。但这感觉太不对劲了。 CDI bean 不应该知道它自己的请求吗?

我错过了什么?

【问题讨论】:

【参考方案1】:

提取 HTTP 标头

您不需要在 JAX-RS 端点中使用 HttpServletRequest 来从请求中获取 HTTP 标头。相反,你可以注入HttpHeaders:

@Context
HttpHeaders httpHeaders;

然后你可以使用HttpHeaders API 来获取标题值:

HttpHeaders#getHeaderString(String) HttpHeaders#getRequestHeaders() HttpHeaders#getHeaderString(String)

如果您需要标准 HTTP 标头的值,请考虑使用 constants available in the HttpHeaders API:

// Get the value of the Authorization header
String authorizationHeader = httpHeaders.getHeaderString(HttpHeaders.AUTHORIZATION);

使用过滤器

由于您正在执行身份验证和/或授权,我建议您使用过滤器,这样您就可以保持 REST 端点精简并专注于业务逻辑。

为了将过滤器绑定到您的 REST 端点,JAX-RS 提供了元注释@NameBinding,可以按如下方式使用:

@NameBinding
@Retention(RUNTIME)
@Target(TYPE, METHOD)
public @interface Secured  

@Secured 注解将用于装饰一个过滤器类,它实现了ContainerRequestFilter,允许您处理请求。

ContainerRequestContext 帮助您从 HTTP 请求中提取信息(更多详细信息,请查看ContainerRequestContext API):

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

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException 
        // Use the ContainerRequestContext to extract information from the HTTP request
        // Information such as the URI, headers and HTTP entity are available
    

如果用户未经过身份验证/授权,ContainerRequestFilter#filter() 方法是中止请求的好地方。为此,您可以使用ContainerRequestContext#abortWith() 或抛出异常。

@Provider 注释标记了扩展接口的实现,在提供程序扫描阶段应该可以被 JAX-RS 运行时发现。

要将过滤器绑定到您的端点方法或类,请使用上面创建的@Secured 注释对其进行注释。对于被注释的方法和/或类,将执行过滤器。

@Path("/")
public class MyEndpoint 

    @GET
    @Path("id")
    @Produces("application/json")
    public Response myUnsecuredMethod(@PathParam("id") Long id) 
        // This method is not annotated with @Secured
        // The security filter won't be executed before invoking this method
        ...
    

    @DELETE
    @Secured
    @Path("id")
    @Produces("application/json")
    public Response mySecuredMethod(@PathParam("id") Long id) 
        // This method is annotated with @Secured
        // The security filter will be executed before invoking this method
        ...
    

在上面的示例中,安全过滤器将仅针对mySecuredMethod(Long) 执行,因为它带有@Secured 注释。

您可以为 REST 端点设置任意数量的过滤器。为确保过滤器的执行顺序,请使用@Priority 对其进行注释。

强烈建议使用Priorities 类中定义的值之一(将使用以下顺序):

AUTHENTICATION AUTHORIZATION ENTITY_CODER HEADER_DECORATOR USER

如果您的过滤器未使用@Priority 注释,则过滤器将以USER 优先级执行。

其他信息

您可能会发现这个answer 很有用。

【讨论】:

我认为您在回复的第一部分误解了我 - 我 可以 获取 JAX-RS 类中的标头,而不是直接在 CDI bean 中,因此我的当前将 HttpServletRequest 传递给 bean 的方法。但我确实喜欢您使用名称绑定和注释而不是注入 bean 的想法。我明天试一试,然后回来报告! 还没有时间,抱歉。 这个答案为我做了!谢谢。【参考方案2】:

根据 JAX-RS 实现和您使用的服务器,您可能需要一些依赖项来提供 CDI 和 JAX-RS 之间的集成:

Jersey Ext Cdi1x

<dependency>
    <groupId>org.glassfish.jersey.ext.cdi</groupId>
    <artifactId>jersey-cdi1x</artifactId>
    <version>2.22.1</version>
</dependency>

RESTEasy CDI Integration Module

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-cdi</artifactId>
    <version>3.0.13.Final</version>
</dependency>

Apache CXF CDI Integration

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-integration-cdi</artifactId>
    <version>3.1.3</version>
</dependency>

【讨论】:

该项目目前在 EE6 上运行 RESTEasy 3.0.4,并部署在 EAP 6.3 上。我也在这里读到,注入应该在 EE7 中开箱即用,所以我尝试升级,但在部署时仍然出现 WELD 错误。有时间我会试试这些集成商。 @Antares42 Weld 堆栈跟踪将很有用。在 Java EE 7 (CDI 1.1) 中直接支持HttpServletRequest 的注入。在 Java EE 6 (CDI 1.0) 中,您没有开箱即用的此功能。您可以做的是创建一个ServletRequestListener,将HttpServletRequest 存储在ThreadLocal 中,并为HttpServletRequest 创建一个CDI 生产者方法。

以上是关于如何在注入 JAX-RS Web 服务的 CDI bean 中获取 HTTP 请求标头?的主要内容,如果未能解决你的问题,请参考以下文章

无法让CDI和JAX-RS在Glassfish中协同工作

如何在 CDI 中使用 MyBatis

如何使用 CDI 进行方法参数注入?

Servlet CDI

将 EJB 注入 JAX-RS(RESTful 服务)

servlet CDI