Лучшая практика аутентификации на основе токенов REST с JAX-RS и Джерси

Я ищу способ включить аутентификацию на токенах в Джерси. Я стараюсь не использовать какие-либо конкретные рамки. Возможно ли это?

Мой план: пользователь подписывается на мой веб-сервис, мой веб-сервис генерирует токен, отправляет его клиенту, и клиент сохранит его. Затем клиент для каждого запроса отправляет токен вместо имени пользователя и пароля.

Я думал об использовании настраиваемого фильтра для каждого запроса и @PreAuthorize("hasRole('ROLE')") но я просто подумал, что это вызывает много запросов к базе данных, чтобы проверить, действительно ли токен.

Или не создавать фильтр и в каждом запросе поставить маркер param? Чтобы каждый API сначала проверял токен и после чего выполнял что-то для извлечения ресурса.

Ответ 1

Как работает аутентификация на токенах

При аутентификации на токенах клиент обменивает жесткие учетные данные (например, имя пользователя и пароль) для части данных, называемой токеном. Для каждого запроса вместо отправки жестких учетных данных клиент отправляет токен на сервер для выполнения проверки подлинности и авторизации.

В нескольких словах схема аутентификации, основанная на токенах, выполняет следующие шаги:

  1. Клиент отправляет свои учетные данные (имя пользователя и пароль) на сервер.
  2. Сервер аутентифицирует учетные данные и, если они действительны, генерирует токен для пользователя.
  3. Сервер хранит ранее сгенерированный токен в некотором хранилище вместе с идентификатором пользователя и датой истечения срока действия.
  4. Сервер отправляет сгенерированный токен клиенту.
  5. Клиент отправляет маркер на сервер в каждом запросе.
  6. Сервер в каждом запросе извлекает токен из входящего запроса. С помощью маркера сервер просматривает детали пользователя для выполнения проверки подлинности.
    • Если токен действителен, сервер принимает запрос.
    • Если токен недействителен, сервер отказывается от запроса.
  7. После проверки подлинности сервер выполняет авторизацию.
  8. Сервер может предоставить конечную точку для обновления токенов.

Примечание. Шаг 3 не требуется, если сервер выпустил подписанный токен (например, JWT, который позволяет выполнять аутентификацию без сохранения).

Что вы можете сделать с JAX-RS 2.0 (Джерси, RESTEasy и Apache CXF)

Это решение использует только API JAX-RS 2.0, избегая любого конкретного решения для конкретного поставщика. Таким образом, он должен работать с реализациями JAX-RS 2.0, такими как Jersey, RESTEasy и Apache CXF.

Стоит упомянуть, что если вы используете аутентификацию на основе токенов, вы не полагаетесь на стандартные механизмы безопасности веб-приложений Java EE, предлагаемые контейнером сервлетов, и настраиваются через web.xml приложения web.xml. Это обычная проверка подлинности.

Аутентификация пользователя с их именем пользователя и паролем и выдачей токена

Создайте метод ресурса JAX-RS, который получает и проверяет учетные данные (имя пользователя и пароль) и выдает токен пользователю:

@Path("/authentication")
public class AuthenticationEndpoint {

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response authenticateUser(@FormParam("username") String username, 
                                     @FormParam("password") String password) {

        try {

            // Authenticate the user using the credentials provided
            authenticate(username, password);

            // Issue a token for the user
            String token = issueToken(username);

            // Return the token on the response
            return Response.ok(token).build();

        } catch (Exception e) {
            return Response.status(Response.Status.FORBIDDEN).build();
        }      
    }

    private void authenticate(String username, String password) throws Exception {
        // Authenticate against a database, LDAP, file or whatever
        // Throw an Exception if the credentials are invalid
    }

    private String issueToken(String username) {
        // Issue a token (can be a random String persisted to a database or a JWT token)
        // The issued token must be associated to a user
        // Return the issued token
    }
}

Если при проверке учетных данных выбрасываются какие-либо исключения, возвращается ответ со статусом 403 (Запрещено).

Если учетные данные успешно подтверждены, ответ со статусом 200 (OK) будет возвращен, и выданный токен будет отправлен клиенту в полезной нагрузке ответа. Клиент должен отправить маркер на сервер в каждом запросе.

При использовании application/x-www-form-urlencoded клиент должен отправить учетные данные в следующем формате в полезной нагрузке запроса:

username=admin&password=123456

Вместо параметров формы можно связать имя пользователя и пароль с классом:

public class Credentials implements Serializable {

    private String username;
    private String password;

    // Getters and setters omitted
}

И затем потребляйте его как JSON:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response authenticateUser(Credentials credentials) {

    String username = credentials.getUsername();
    String password = credentials.getPassword();

    // Authenticate the user, issue a token and return a response
}

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

{
  "username": "admin",
  "password": "123456"
}

Извлечение маркера из запроса и его проверка

Клиент должен отправить токен в стандартный заголовок HTTP- Authorization запроса. Например:

Authorization: Bearer <token-goes-here>

Имя стандартного HTTP-заголовка является неудачным, поскольку оно содержит информацию аутентификации, а не авторизацию. Тем не менее, это стандартный HTTP-заголовок для отправки учетных данных на сервер.

JAX-RS предоставляет @NameBinding, мета-аннотацию, используемую для создания других аннотаций для привязки фильтров и перехватчиков к классам ресурсов и методам. Определите аннотацию @Secured следующим образом:

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured { }

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

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    private static final String REALM = "example";
    private static final String AUTHENTICATION_SCHEME = "Bearer";

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        // Get the Authorization header from the request
        String authorizationHeader =
                requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

        // Validate the Authorization header
        if (!isTokenBasedAuthentication(authorizationHeader)) {
            abortWithUnauthorized(requestContext);
            return;
        }

        // Extract the token from the Authorization header
        String token = authorizationHeader
                            .substring(AUTHENTICATION_SCHEME.length()).trim();

        try {

            // Validate the token
            validateToken(token);

        } catch (Exception e) {
            abortWithUnauthorized(requestContext);
        }
    }

    private boolean isTokenBasedAuthentication(String authorizationHeader) {

        // Check if the Authorization header is valid
        // It must not be null and must be prefixed with "Bearer" plus a whitespace
        // The authentication scheme comparison must be case-insensitive
        return authorizationHeader != null && authorizationHeader.toLowerCase()
                    .startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
    }

    private void abortWithUnauthorized(ContainerRequestContext requestContext) {

        // Abort the filter chain with a 401 status code response
        // The WWW-Authenticate header is sent along with the response
        requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED)
                        .header(HttpHeaders.WWW_AUTHENTICATE, 
                                AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
                        .build());
    }

    private void validateToken(String token) throws Exception {
        // Check if the token was issued by the server and if it not expired
        // Throw an Exception if the token is invalid
    }
}

Если во время проверки маркера возникают какие-либо проблемы, возвращается ответ со статусом 401 (Несанкционированный). В противном случае запрос перейдет к методу ресурса.

Защита конечных точек REST

Чтобы связать фильтр проверки подлинности с методами ресурсов или классами ресурсов, аннотируйте их с @Secured аннотации @Secured созданной выше. Для методов и/или классов, которые аннотируются, фильтр будет выполнен. Это означает, что такие конечные точки будут достигнуты только в том случае, если запрос выполняется с действительным токеном.

Если некоторым методам или классам не требуется аутентификация, просто не комментируйте их:

@Path("/example")
public class ExampleResource {

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response myUnsecuredMethod(@PathParam("id") Long id) {
        // This method is not annotated with @Secured
        // The authentication filter won't be executed before invoking this method
        ...
    }

    @DELETE
    @Secured
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response mySecuredMethod(@PathParam("id") Long id) {
        // This method is annotated with @Secured
        // The authentication filter will be executed before invoking this method
        // The HTTP request must be performed with a valid token
        ...
    }
}

В приведенном выше примере фильтр будет выполняться только для mySecuredMethod(Long) поскольку он аннотируется с помощью @Secured.

Идентификация текущего пользователя

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

Переопределение контекста безопасности текущего запроса

В вашем методе ContainerRequestFilter.filter(ContainerRequestContext) для текущего запроса может быть установлен новый экземпляр SecurityContext. Затем переопределите SecurityContext.getUserPrincipal(), возвращая экземпляр Principal:

final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {

        @Override
        public Principal getUserPrincipal() {
            return () -> username;
        }

    @Override
    public boolean isUserInRole(String role) {
        return true;
    }

    @Override
    public boolean isSecure() {
        return currentSecurityContext.isSecure();
    }

    @Override
    public String getAuthenticationScheme() {
        return AUTHENTICATION_SCHEME;
    }
});

Используйте токен для поиска идентификатора пользователя (имени пользователя), который будет именем Principal.

Внедрение SecurityContext в любой класс ресурсов JAX-RS:

@Context
SecurityContext securityContext;

То же самое можно сделать в методе ресурса JAX-RS:

@GET
@Secured
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myMethod(@PathParam("id") Long id, 
                         @Context SecurityContext securityContext) {
    ...
}

И затем получите Principal:

Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();

Использование CDI (интродукция контекста и зависимостей)

Если по какой-то причине вы не хотите переопределять SecurityContext, вы можете использовать CDI (Context и Dependency Injection), который предоставляет полезные функции, такие как события и производители.

Создайте квалификатор CDI:

@Qualifier
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER })
public @interface AuthenticatedUser { }

В созданном выше AuthenticationFilter @AuthenticatedUser Event аннотированное с помощью @AuthenticatedUser:

@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;

Если аутентификация удалась, запустите событие, передающее имя пользователя в качестве параметра (помните, токен выдается для пользователя, и токен будет использоваться для поиска идентификатора пользователя):

userAuthenticatedEvent.fire(username);

Очень вероятно, что существует класс, представляющий пользователя в вашем приложении. Позвольте назвать этот класс User.

Создайте компонент CDI для обработки события аутентификации, найдите экземпляр User с соответствующим именем пользователя и назначьте его в поле производителя authenticatedUser:

@RequestScoped
public class AuthenticatedUserProducer {

    @Produces
    @RequestScoped
    @AuthenticatedUser
    private User authenticatedUser;

    public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username) {
        this.authenticatedUser = findUser(username);
    }

    private User findUser(String username) {
        // Hit the the database or a service to find a user by its username and return it
        // Return the User instance
    }
}

Поле authenticatedUser создает экземпляр User который может быть введен в контейнерные управляемые компоненты, такие как службы JAX-RS, компоненты CDI, сервлеты и EJB. Используйте следующий фрагмент кода для ввода экземпляра User (на самом деле это прокси-сервер CDI):

@Inject
@AuthenticatedUser
User authenticatedUser;

Обратите внимание, что аннотация CDI @Produces отличается от аннотации JAX-RS @Produces:

Убедитесь, что вы используете аннотацию CDI @Produces в компоненте AuthenticatedUserProducer.

Ключевым моментом здесь является bean, аннотированный с помощью @RequestScoped, позволяющий обмениваться данными между фильтрами и вашими бобами. Если вы не хотите использовать события, вы можете изменить фильтр для хранения аутентифицированного пользователя в компоненте с включенным запросом, а затем прочитать его из классов ресурсов JAX-RS.

По сравнению с подходом, который переопределяет SecurityContext, подход CDI позволяет получить аутентифицированного пользователя из компонентов, отличных от ресурсов и поставщиков JAX-RS.

Поддержка ролевой авторизации

Дополнительную информацию о том, как поддерживать авторизацию на основе ролей, см. В моем другом ответе.

Выдача токенов

Токен может быть:

  • Opaque: не показывает никаких деталей, кроме самого значения (например, случайной строки)
  • Автономный: содержит информацию о самом марке (например, JWT).

См. Подробности ниже:

Случайная строка как токен

Маркер может быть выпущен путем создания случайной строки и сохранения ее в базе данных вместе с идентификатором пользователя и датой истечения срока действия. Хороший пример того, как создать случайную строку в Java, можно увидеть здесь. Вы также можете использовать:

Random random = new SecureRandom();
String token = new BigInteger(130, random).toString(32);

JWT (JSON Web Token)

JWT (JSON Web Token) является стандартным методом для надежного представления претензий между двумя сторонами и определяется RFC 7519.

Это автономный токен, который позволяет хранить детали в претензиях. Эти претензии сохраняются в полезной нагрузке маркера, которая является JSON, закодированной как Base64. Вот некоторые заявки, зарегистрированные в RFC 7519 и что они означают (прочитайте полный RFC для получения дополнительной информации):

  • iss: Принципал, выдавший токен.
  • sub: Principal, который является предметом JWT.
  • exp: дата окончания токена.
  • nbf: время, когда токен начнет принимать для обработки.
  • iat: Время, на которое был выдан токен.
  • jti: уникальный идентификатор для токена.

Имейте в виду, что вы не должны хранить конфиденциальные данные, такие как пароли, в токене.

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

Вам не нужно будет сохранять токены JWT, если вам не нужно их отслеживать. Хотя, сохраняя токены, у вас будет возможность аннулировать и отменить доступ к ним. Чтобы сохранить следы токенов JWT, вместо того, чтобы сохранять весь токен на сервере, вы можете сохранить идентификатор маркера (требование jti) вместе с некоторыми другими данными, такими как пользователь, которому вы выдали токен, дату истечения срока и т.д.

При сохранении токенов всегда рекомендуется удалять старые, чтобы ваша база данных не увеличивалась бесконечно.

Использование JWT

Существует несколько библиотек Java для выдачи и проверки токенов JWT, таких как:

Чтобы найти другие полезные ресурсы для работы с JWT, посмотрите на http://jwt.io.

Обработка обновления токена с помощью JWT

Принять только действительные (и не истекшие) токены для обновления. Ответственность клиента за обновление токенов до даты истечения срока действия, указанной в заявке на exp.

Вы должны помешать токенам обновляться на неопределенный срок. Ниже вы найдете несколько подходов, которые вы могли бы рассмотреть.

Вы можете сохранить трек обновления токена, добавив две претензии к вашему токену (имена претензий зависят от вас):

  • refreshLimit: указывает, сколько раз токен может быть обновлен.
  • refreshCount: указывает, сколько раз токен был обновлен.

Так что только обновите токен, если выполняются следующие условия:

  • Токен не истек (exp >= now).
  • Количество refreshCount < refreshLimit обновления токена меньше количества раз, когда токен может быть обновлен (refreshCount < refreshLimit).

И при обновлении токена:

  • Обновите дату истечения срока действия (exp = now + some-amount-of-time).
  • refreshCount++ обновления токена (refreshCount++).

В качестве альтернативы для отслеживания количества напитков вам может потребоваться заявка, указывающая абсолютную дату истечения срока действия (которая очень похожа на refreshLimit описанную выше). До абсолютной даты истечения срока допустимо любое количество прохладительных напитков.

Другой подход включает в себя выпуск отдельного долгоживущего токена обновления, который используется для выдачи недолговечных токенов JWT.

Наилучший подход зависит от ваших требований.

Обработка аннулирования токена с помощью JWT

Если вы хотите отменить токены, вы должны следить за ними. Вам не нужно хранить весь токен на стороне сервера, хранить только идентификатор маркера (который должен быть уникальным) и некоторые метаданные, если вам нужно. Для идентификатора маркера вы можете использовать UUID.

Требование jti должно использоваться для хранения идентификатора маркера на токене. При проверке маркера убедитесь, что он не был отозван, проверяя значение требования jti против идентификаторов маркера, которые у вас есть на стороне сервера.

В целях безопасности отмените все токены для пользователя, когда они меняют свой пароль.

Дополнительная информация

  • Неважно, какой тип аутентификации вы решите использовать. Всегда делайте это на верхней части HTTPS-соединения, чтобы предотвратить атаку " человек-в-середине".
  • Взгляните на этот вопрос из Information Security за дополнительной информацией о токенах.
  • В этой статье вы найдете полезную информацию об аутентификации на токенах.

Ответ 2

Этот ответ касается авторизации, и это дополнение моего предыдущего ответа об аутентификации

Зачем нужен другой ответ?Я попытался расширить свой предыдущий ответ, добавив подробности о том, как поддерживать аннотации JSR-250.Однако исходный ответ стал слишком длинным и превысил максимальную длину в 30 000 символов.Поэтому я переместил все данные авторизации на этот ответ, оставив другой ответ сосредоточенным на выполнении аутентификации и выдаче токенов.


Поддержка ролевой авторизации с @Secured аннотации @Secured

Помимо потока проверки подлинности, указанного в другом ответе, авторизация на основе ролей может поддерживаться в конечных точках REST.

Создайте перечисление и определите роли в соответствии с вашими потребностями:

public enum Role {
    ROLE_1,
    ROLE_2,
    ROLE_3
}

Измените аннотацию привязки @Secured созданную ранее, для поддержки ролей:

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured {
    Role[] value() default {};
}

Затем аннотируйте классы ресурсов и методы с помощью @Secured для выполнения авторизации. Аннотации метода переопределяют аннотации классов:

@Path("/example")
@Secured({Role.ROLE_1})
public class ExampleResource {

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response myMethod(@PathParam("id") Long id) {
        // This method is not annotated with @Secured
        // But it declared within a class annotated with @Secured({Role.ROLE_1})
        // So it only can be executed by the users who have the ROLE_1 role
        ...
    }

    @DELETE
    @Path("{id}")    
    @Produces(MediaType.APPLICATION_JSON)
    @Secured({Role.ROLE_1, Role.ROLE_2})
    public Response myOtherMethod(@PathParam("id") Long id) {
        // This method is annotated with @Secured({Role.ROLE_1, Role.ROLE_2})
        // The method annotation overrides the class annotation
        // So it only can be executed by the users who have the ROLE_1 or ROLE_2 roles
        ...
    }
}

Создайте фильтр с приоритетом AUTHORIZATION, который выполняется после фильтра приоритета AUTHENTICATION определенного ранее.

ResourceInfo может использоваться для получения ресурса Method и Class ресурсов, который будет обрабатывать запрос, а затем извлечь из них аннотации @Secured:

@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        // Get the resource class which matches with the requested URL
        // Extract the roles declared by it
        Class<?> resourceClass = resourceInfo.getResourceClass();
        List<Role> classRoles = extractRoles(resourceClass);

        // Get the resource method which matches with the requested URL
        // Extract the roles declared by it
        Method resourceMethod = resourceInfo.getResourceMethod();
        List<Role> methodRoles = extractRoles(resourceMethod);

        try {

            // Check if the user is allowed to execute the method
            // The method annotations override the class annotations
            if (methodRoles.isEmpty()) {
                checkPermissions(classRoles);
            } else {
                checkPermissions(methodRoles);
            }

        } catch (Exception e) {
            requestContext.abortWith(
                Response.status(Response.Status.FORBIDDEN).build());
        }
    }

    // Extract the roles from the annotated element
    private List<Role> extractRoles(AnnotatedElement annotatedElement) {
        if (annotatedElement == null) {
            return new ArrayList<Role>();
        } else {
            Secured secured = annotatedElement.getAnnotation(Secured.class);
            if (secured == null) {
                return new ArrayList<Role>();
            } else {
                Role[] allowedRoles = secured.value();
                return Arrays.asList(allowedRoles);
            }
        }
    }

    private void checkPermissions(List<Role> allowedRoles) throws Exception {
        // Check if the user contains one of the allowed roles
        // Throw an Exception if the user has not permission to execute the method
    }
}

Если у пользователя нет разрешения на выполнение операции, запрос прерывается с 403 (Запрещено).

Чтобы узнать пользователя, выполняющего запрос, см. Мой предыдущий ответ. Вы можете получить его из SecurityContext (который должен быть уже установлен в ContainerRequestContext) или использовать его с помощью CDI, в зависимости от подхода, для которого вы идете.

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

Поддержка ролевой авторизации с аннотациями JSR-250

В качестве альтернативы для определения ролей в аннотации @Secured как показано выше, вы можете рассмотреть аннотации JSR-250, такие как @RolesAllowed, @PermitAll и @DenyAll.

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

Таким образом, фильтр авторизации, который проверяет аннотации JSR-250, может быть таким:

@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        Method method = resourceInfo.getResourceMethod();

        // @DenyAll on the method takes precedence over @RolesAllowed and @PermitAll
        if (method.isAnnotationPresent(DenyAll.class)) {
            refuseRequest();
        }

        // @RolesAllowed on the method takes precedence over @PermitAll
        RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
        if (rolesAllowed != null) {
            performAuthorization(rolesAllowed.value(), requestContext);
            return;
        }

        // @PermitAll on the method takes precedence over @RolesAllowed on the class
        if (method.isAnnotationPresent(PermitAll.class)) {
            // Do nothing
            return;
        }

        // @DenyAll can't be attached to classes

        // @RolesAllowed on the class takes precedence over @PermitAll on the class
        rolesAllowed = 
            resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
        if (rolesAllowed != null) {
            performAuthorization(rolesAllowed.value(), requestContext);
        }

        // @PermitAll on the class
        if (resourceInfo.getResourceClass().isAnnotationPresent(PermitAll.class)) {
            // Do nothing
            return;
        }

        // Authentication is required for non-annotated methods
        if (!isAuthenticated(requestContext)) {
            refuseRequest();
        }
    }

    /**
     * Perform authorization based on roles.
     *
     * @param rolesAllowed
     * @param requestContext
     */
    private void performAuthorization(String[] rolesAllowed, 
                                      ContainerRequestContext requestContext) {

        if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
            refuseRequest();
        }

        for (final String role : rolesAllowed) {
            if (requestContext.getSecurityContext().isUserInRole(role)) {
                return;
            }
        }

        refuseRequest();
    }

    /**
     * Check if the user is authenticated.
     *
     * @param requestContext
     * @return
     */
    private boolean isAuthenticated(final ContainerRequestContext requestContext) {
        // Return true if the user is authenticated or false otherwise
        // An implementation could be like:
        // return requestContext.getSecurityContext().getUserPrincipal() != null;
    }

    /**
     * Refuse the request.
     */
    private void refuseRequest() {
        throw new AccessDeniedException(
            "You don't have permissions to perform this action.");
    }
}

Примечание. Вышеупомянутая реализация основана на модели RolesAllowedDynamicFeature.Если вы используете Джерси, вам не нужно писать собственный фильтр, просто используйте существующую реализацию.