Аутентификация / авторизация в 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 не может быть создан по какой-либо причине.
Теперь мои вопросы:
- Это хороший дизайн? Если нет, какие варианты лучше?
- Как мне этого добиться? Мне все равно, если мое решение не относится к 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();
}
Другие способы решения вашей проблемы:
- Зарегистрируйте WebFilter, аутентифицируйте пользователя, оберните ServletRequest и переопределите getUserPrincipal. Затем вы можете получить доступ к UserPrincipal из введенного запроса HttpServletRequest.
- Реализуйте JAX-RS Interceptor, который реализует ContainerRequestFilter. Используйте ContainerRequestContext.html#setSecurityContext с UserPrincipal и введите SecurityContext в качестве ResourceMethod-Parameter.
- Реализуйте CDI-Interceptor, который обновляет ваши Method-Parameters.
- Реализуйте класс, который создает вашего пользователя, и внедрите его через 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();
}