Аутентификация / авторизация в JAX-RS с использованием перехватчиков и инъекций

Я разрабатываю новое приложение на JavaEE 7 с использованием WildFly 8. Я использую JAX-RS для предоставления интерфейса службы RESTful для удаленных приложений.

Что-то вроде HttpHeaders объект может быть введен в аргументы метода ресурса, используя @Context аннотаций. Так как объект основан на параметрах запроса (конечно, заголовки HTTP), у меня возникла идея создать свой собственный инъекционный User объект, который создается на основе наличия в запросе действительного токена (что-то вроде токена доступа OAuth).

Итак, я хочу добиться чего-то вроде этого:

@Path("/resources")
public class MyResource {

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

}

Где Пользователь - это инъецируемый объект, созданный на основе параметров запроса, например, доступных через HttpHeaders объект. Конечно, провайдер также может выдать исключение и вернуть ответ об ошибке HTTP, если объект User не может быть создан по какой-либо причине.

Теперь мои вопросы:

  1. Это хороший дизайн? Если нет, какие варианты лучше?
  2. Как мне этого добиться? Мне все равно, если мое решение не относится к JAX-RS и использует внутренние компоненты, относящиеся к WildFly/RestEasy, но я определенно предпочитаю портативное решение, если оно существует.

Спасибо

2 ответа

Решение

На мой взгляд, этот подход действителен, если вы не пытаетесь создать что-то вроде сеанса с этим объектом пользователя.

Как ответили здесь, вы можете использовать @Context и @Provider, но это не совсем то, что вы хотите. Непосредственное введение класса в @Context возможно с Диспетчером Resteasy. Но здесь вы должны зарегистрировать объект, который должен быть введен. Я не думаю, что это имеет смысл для параметров области запроса. То, что вы могли бы сделать, это ввести поставщика, как это:

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

Другие способы решения вашей проблемы:

  1. Зарегистрируйте WebFilter, аутентифицируйте пользователя, оберните ServletRequest и переопределите getUserPrincipal. Затем вы можете получить доступ к UserPrincipal из введенного запроса HttpServletRequest.
  2. Реализуйте JAX-RS Interceptor, который реализует ContainerRequestFilter. Используйте ContainerRequestContext.html#setSecurityContext с UserPrincipal и введите SecurityContext в качестве ResourceMethod-Parameter.
  3. Реализуйте CDI-Interceptor, который обновляет ваши Method-Parameters.
  4. Реализуйте класс, который создает вашего пользователя, и внедрите его через CDI.

Я подтолкнул примеры в GitHub.

Хотя ответ Лефло определенно верен, я бы хотел остановиться на его втором подходе, который я считаю наиболее удобным.

Вы должны создать Interceptor(Filter), который реализует 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);
    }
}

Вам нужно будет либо зарегистрировать его, установив автообнаружение в файле 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();
}
Другие вопросы по тегам