使用拦截器和注入在 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 Jersey 进行身份验证和授权的简便方法
使用 JAX-RS Jersey 进行身份验证和授权的简便方法
Java:使用带有 Spring Security 的 Jersey Jax-RS 进行用户身份验证