Обеспечение безопасности REST-сервисов в Джерси

Я очень новичок в веб-сервисах. Я представил некоторые службы REST, используя Jersey 2 в интеграции с Spring. Теперь мне нужно защитить эти остальные службы, используя аутентификацию с именем пользователя/паролем. Мне сказали не использовать Spring Security.

Я понятия не имею, как это сделать. Я искал в сети, но разные ссылки показывают разные реализации, и я не могу решить, как с этим поступить.


person Anand    schedule 29.01.2015    source источник
comment
Взгляните на stackoverflow.com/questions/10826293/   -  person Hannes    schedule 08.02.2015


Ответы (5)


Распространенным способом аутентификации с использованием имени пользователя и пароля является использование Базовая аутентификация. В основном клиенту необходимо отправить заголовок запроса Authorization со значением заголовка как Basic Base64Encoded(username:password). Итак, мое имя пользователя peeskillet и мой пароль pass, я, как клиент, должен установить заголовок как

Authorization: Basic cGVlc2tpbGxldDpwYXNz

В среде сервлетов контейнер должен поддерживать обычную аутентификацию. Вы должны настроить эту поддержку в файле web.xml. Вы можете увидеть пример в 48.2 Защита веб-приложений учебника по Java EE. Вы также заметите в примере

<transport-guarantee>CONFIDENTIAL</transport-guarantee>

Это для поддержки SSL. Это рекомендуется для базовой аутентификации.

Если вы не хотите иметь дело с проблемами работы с доменами безопасности и модулями входа в систему, областью и т. д., которые потребуются для настройки поддержки сервлетов, или если вы просто не находитесь в среде сервлетов, реализация Basic Auth в ContainerRequestFilter действительно не так уж сложно.

Вы можете увидеть полный пример того, как это можно сделать, по адресу https://github.com/jersey/jersey/tree/master/examples/https-clientserver-grizzly. Вы должны сосредоточиться на SecurityFilter

Основной поток в фильтре выглядит примерно так

  1. Получите заголовок Authorization. Если он не существует, введите AuthenticationException. В этом случае AuthenticationExceptionMapper отправит заголовок "WWW-Authenticate", "Basic realm=\"" + e.getRealm() + "\", который является частью протокола Basic Auth.

  2. Получив заголовок, мы анализируем его, чтобы получить имя пользователя: пароль в кодировке Base64. Затем мы расшифровываем его, затем разделяем, затем разделяем имя пользователя и пароль. Если какой-либо из этих процессов завершается сбоем, снова выдайте WebApplicationException, который соответствует 400 Bad Request.

  3. Проверьте имя пользователя и пароль. Исходный код примера просто проверяет, является ли имя пользователя user и пароль password, но вы захотите использовать какой-либо сервис в фильтре, чтобы проверить эту информацию. Если что-то из этого не работает, бросьте AuthenticationException

  4. Если все идет хорошо, из метода authenticate создается User, который вставляется в Authorizer (то есть SecurityContext). В JAX-RS для авторизации обычно используется SecurityContext.

Для авторизации, если вы хотите защитить определенные области для определенных ресурсов, вы можете использовать аннотацию @RolesAllowed для своих классов или методов. Джерси поддерживает эту аннотацию путем регистрации RolesAllowedDynamicFeature.

Под капотом происходит то, что SecurityContext будет получено из запроса. В примере, на который я ссылаюсь, вы можете увидеть Authorizer, у него есть переопределенный метод isUserInRole. Этот метод будет вызываться для проверки значений в @RolesAllowed({"ADMIN"}). Поэтому, когда вы создаете SecurityContext, вы должны обязательно включить в переопределенный метод роли пользователя.

Для тестирования можно просто использовать браузер. Если все настроено правильно, при попытке доступа к ресурсу вы должны увидеть (в Firefox) диалоговое окно, как показано в этом сообщении< /а>. Если вы используете cURL, вы можете сделать

C:/>curl -v -u username:password http://localhost:8080/blah/resource

Это отправит запрос с базовой проверкой подлинности. Из-за переключателя -v вы должны увидеть все задействованные заголовки. Если вы просто хотите протестировать клиентский API, см. здесь как настроить. В любом из трех упомянутых случаев кодировка Base64 будет сделана за вас, так что вам не нужно об этом беспокоиться.

Что касается SSL, вы должны изучить документацию вашего контейнера для получения информации о том, как его настроить.

person Paul Samsotha    schedule 02.02.2015

Так что это действительно вопрос, чего вы хотели бы достичь. Мое дело заключалось в том, чтобы заставить эту штуку работать с мобильными устройствами и одностраничным приложением JavaScript.

По сути, все, что вам нужно сделать, это сгенерировать какой-то заголовок, значение которого будет необходимо в каждом последующем запросе, который будет делать ваш клиент.

Итак, вы делаете конечную точку, в которой вы ждете сообщения с пользователем/паролем:

@Path("/login")
public class AuthenticationResource {

@POST
@Consumes("application/json")
public Response authenticate(Credentials credential) {
    boolean canBeLoggedIn = (...check in your DB or anywher you need to)

    if (canBeLoggedIn) {
        UUID uuid = UUID.randomUUID();
        Token token = new Token();
        token.setToken(uuid.toString());
        //save your token with associated with user
        (...)

        return Response.ok(token).type(MediaType.APPLICATION_JSON_TYPE).build();
    } else {
        return Response.status(Response.Status.UNAUTHORIZED).build();
    }
}
}

Теперь вам нужно защитить ресурс с помощью этого токена:

   @Path("/payment")
   @AuthorizedWithToken
   public class Payments {

    @GET
    @Produces("application/json")
    public Response sync() {
     (...)
    }

    }

Обратите внимание на аннотацию @AuthorizedWithToken. Эту аннотацию вы можете создать самостоятельно, используя специальную мета-аннотацию @NameBinding

@NameBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorizedWithToken {}

А теперь о фильтре, реализующем проверку заголовка:

@AuthorizedWithToken
@Provider
public class XAuthTokenFilter implements ContainerRequestFilter {

    private static String X_Auth_Token = "X-Auth-Token";

    @Override
    public void filter(ContainerRequestContext crc) throws IOException {
        String headerValue = crc.getHeaderString(X_Auth_Token);
        if (headerValue == null) {
            crc.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Missing " + X_Auth_Token + " value").build());
            return;
        }

        if(! TOKEN_FOUND_IN_DB) {
            crc.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Wrong " + X_Auth_Token + " value").build());
            return;
        }
    }
}

Вы можете создать любое количество собственных аннотаций, проверяющих различные вещи в http-запросе, и смешивать их. Однако вам нужно обратить внимание на приоритеты, но это на самом деле легко найти. Этот метод требует использования https, но это очевидно.

person almendar    schedule 08.02.2015

Безопасность бывает двух основных видов:

  • Контейнер на основе
  • на основе приложений

стандартный способ защитить приложения Spring — использовать Spring Security (ранее Acegi). Было бы интересно узнать, почему вам не разрешено использовать это.

Вы можете использовать безопасность на основе контейнера, но я предполагаю, что использование вами Spring также исключает этот вариант. Поскольку выбор Spring обычно заключается в том, чтобы избежать необходимости использования полного контейнера J2EE (изменить: хотя, как указано ниже другими, большинство обычных контейнеров сервлетов позволяют вам реализовывать различные методы безопасности на основе контейнеров)

На самом деле это оставляет вам только один вариант — установить собственную безопасность.

Ваше использование Джерси предполагает, что это может быть приложение REST. В этом случае вам действительно следует придерживаться стандартных методов HTTP-аутентификации, которые представлены в следующих вариантах в обратном порядке силы:

  • БАЗОВЫЙ
  • Дайджест
  • Форма
  • Сертификат

Обычно предполагается, что приложения REST не имеют состояния, что по существу исключает аутентификацию на основе формы (поскольку вам потребуется использование сеанса), оставляя вам BASIC, Digest и Certificate.

Ваш следующий вопрос: кого я аутентифицирую? Если вы можете ожидать узнать имя пользователя И пароль пользователя на основе того, какой URL-адрес они запросили (скажем, если это один набор учетных данных для всех пользователей), то дайджест - лучший выбор, поскольку пароль никогда не отправляется, только хэш. Если вы не можете знать пароль (потому что вы просите стороннюю систему подтвердить его и т. д.), то вы застряли на BASIC. Но вы можете повысить безопасность BASIC, используя SSL или, что еще лучше, комбинируя BASIC с проверкой подлинности сертификата клиента. На самом деле BASIC-аутентификация через HTTPS является стандартным методом защиты большинства приложений REST.

Вы можете легко реализовать фильтр сервлетов, который ищет заголовок аутентификации и проверяет учетные данные самостоятельно. Есть много примеров таких фильтров, это один автономный файл класса. Если учетные данные не найдены, фильтр возвращает 401, передавая запрос на базовую аутентификацию в заголовках ответа. Если учетные данные недействительны, вы возвращаете 403. Безопасность приложений — это почти целая карьера сама по себе, но я надеюсь, что это поможет.

person Richard    schedule 04.02.2015
comment
Спасибо, Ричард... как вы упомянули в последнем абзаце... вы имеете в виду проверку учетных данных в самом фильтре по некоторым таблицам базы данных, где могут храниться имя пользователя и пароль? - person Anand; 05.02.2015
comment
Да.. Но если вы создадите фильтр внутри Spring, вы можете внедрить в этот фильтр все, что вам нужно для фактической аутентификации (подключение к базе данных и т. д.). Поэтому я, вероятно, рекомендую вам создать фильтр сервлета Spring. - person Richard; 05.02.2015

Как говорится в предыдущих сообщениях, вы можете использовать разные варианты с различными накладными расходами на реализацию. С практической точки зрения, если вы собираетесь начать с этого и ищете удобный способ для простой реализации, я бы рекомендовал вариант на основе контейнера с использованием BASIC-аутентификации.

Если вы используете tomcat, вы можете настроить область, который относительно прост в реализации. Вы можете использовать JDBCRealm, который получает пользователя и пароль из указанных столбцов в вашей базе данных и настраивает их через server.xml и web.xml. Это будет автоматически запрашивать учетные данные каждый раз, когда вы пытаетесь получить доступ к своему приложению. Для этого у вас нет никакой реализации на стороне приложения.

person ulrich    schedule 04.02.2015

Что я могу сказать вам сейчас, так это то, что вы уже сделали большую часть «грязной» работы по интеграции Джерси со Spring. Я рекомендую вам использовать решение на основе приложений, если оно не привязывает вас к конкретному контейнеру. Поначалу Spring Security может пугать, но потом, когда вы приручите зверя, вы увидите, что на самом деле это был дружелюбный щенок.

Дело в том, что Spring Security очень настраиваемый, просто реализуя их интерфейсы. И есть много документации и поддержки. Кроме того, у вас уже есть приложение на основе Spring.

Поскольку все, что вам нужно, это руководство, я могу предоставить вам несколько руководств. Вы можете воспользоваться преимуществами этого блога.

http://www.baeldung.com/rest-with-spring-series/ http://www.baeldung.com/2011/10/31/securing-a-restful-web-service-with-spring-security-3-1-part-3/

person Gabriel Espinel    schedule 05.02.2015