如何在注入 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 请求标头?的主要内容,如果未能解决你的问题,请参考以下文章