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

459

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

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

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

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

DevOps85
источник

Ответы:

1389

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

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

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

  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дескриптора приложения . Это пользовательская аутентификация.

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

Создайте метод ресурса 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будет возвращен ответ со статусом (ОК), а выданный токен будет отправлен клиенту в полезной нагрузке ответа. Клиент должен отправлять токен на сервер при каждом запросе.

При использовании 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's not expired
        // Throw an Exception if the token is invalid
    }
}

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

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

Чтобы привязать фильтр аутентификации к методам ресурсов или классам ресурсов, аннотируйте их @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 (внедрение контекста и зависимости), которое предоставляет полезные функции, такие как события и производители.

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

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

В AuthenticationFilterсозданный выше, введите 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 бобов, сервлетов и EJBs. Используйте следующий фрагмент кода для внедрения Userэкземпляра (на самом деле это прокси-сервер CDI):

@Inject
@AuthenticatedUser
User authenticatedUser;

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

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

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

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

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

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

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

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

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

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

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

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

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

JWT (веб-токен JSON)

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

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

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

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

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

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

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

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

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

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

Обработка отзыва токенов с помощью JWT

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

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

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

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

  • Неважно, какой тип аутентификации вы решите использовать. Всегда делайте это в верхней части HTTPS-соединения, чтобы предотвратить атаку «человек посередине» .
  • Посмотрите на этот вопрос в Информационной безопасности для получения дополнительной информации о токенах.
  • В этой статье вы найдете полезную информацию об аутентификации на основе токенов.
cassiomolin
источник
The server stores the previously generated token in some storage along with the user identifier and an expiration date. The server sends the generated token to the client. Как это RESTful?
Скоттиссей
3
@scottyseus Аутентификация на основе токенов работает по тому, как сервер запоминает выданный токен. Вы можете использовать токены JWT для аутентификации без сохранения состояния.
Кассиомолин
Как насчет отправки хешированного пароля вместо простого (хешируется сгенерированным сервером одноразовым номером)? Повышает ли он уровень безопасности (например, когда не используется https)? В случае человека посередине - он сможет перехватить одну сессию, но, по крайней мере, он не получит пароль
Денис Ицкович
15
Я не могу поверить, что это не в официальной документации.
Даниэль М.
2
@grep В REST нет такой вещи как сеанс на стороне сервера. Следовательно, состояние сеанса управляется на стороне клиента.
Кассиомолин
99

Этот ответ полностью посвящен авторизации и является дополнением к моему предыдущему ответу об аутентификации.

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


Поддержка авторизации на основе ролей с @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's 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аннотации нет объявленных ролей, можно предположить, что все прошедшие проверку пользователи могут получить доступ к этой конечной точке, независимо от ролей, которые имеют пользователи.

Поддержка авторизации на основе ролей с аннотациями 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. Если вы используете Джерси, вам не нужно писать свой собственный фильтр, просто используйте существующую реализацию.

cassiomolin
источник
Есть ли какой-нибудь репозиторий github с этим элегантным решением?
Даниэль Феррейра Кастро
7
@DanielFerreiraCastro Конечно. Посмотрите здесь .
Кассиомолин
Есть ли хороший способ проверить, что запрос от авторизованного пользователя и что пользователь может изменить данные, потому что он «владеет» данными (например, так что хакер не может использовать свой токен для изменения имени другого пользователя)? Я знаю, что на каждой конечной точке я могу проверить, если user_id== token.userIdили что-то в этом роде, но это очень повторяется.
mFeinstein
@mFeinstein Ответ для этого, безусловно, потребует больше символов, чем я могу напечатать здесь в комментариях. Просто чтобы дать вам какое-то направление, вы можете искать безопасность на уровне строк .
Кассиомолин
Я вижу много тем о базах данных, и когда я ищу безопасность на уровне строк, я открою это как новый вопрос
mFeinstein