Найкраща практика для аутентифікації на основі токенів REST з JAX-RS та Jersey


459

Я шукаю спосіб увімкнути аутентифікацію на основі токенів у Джерсі. Я намагаюся не використовувати якісь конкретні рамки. Це можливо?

Мій план такий: Користувач підписується на мою веб-службу, мій веб-сервіс генерує маркер, надсилає його клієнту, і клієнт збереже його. Тоді клієнт для кожного запиту надсилатиме маркер замість імені користувача та пароля.

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

Або не створити фільтр і в кожен запит поставити маркер парам? Так що кожен API спочатку перевіряє маркер і після чого виконує щось для отримання ресурсу.

Відповіді:


1387

Як працює автентифікація на основі лексем

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

Кількома словами схема аутентифікації на основі лексем виконайте наступні кроки:

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

Примітка: крок 3 не потрібен, якщо сервер видав підписаний маркер (наприклад, JWT, який дозволяє виконувати автентифікацію без стану ).

Що ви можете зробити з JAX-RS 2.0 (Jersey, RESTEasy та Apache CXF)

Це рішення використовує лише JAX-RS 2.0 API, уникаючи будь-якого конкретного рішення для постачальника . Отже, він повинен працювати з реалізаціями 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"
}

Вилучення маркера із запиту та перевірка його

Клієнт повинен надіслати маркер у стандартному Authorizationзаголовку HTTP запиту. Наприклад:

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, що дозволяє обмінюватися даними між фільтрами та вашими зернами. Якщо ви не хочете використовувати події, ви можете змінити фільтр, щоб зберігати автентифікованого користувача в обміні запиту, а потім прочитати його з ваших ресурсних класів 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-з'єднання, щоб запобігти атаці "людина-посеред" .
  • Перегляньте це питання з інформаційної безпеки для отримання додаткової інформації про маркери.
  • У цій статті ви знайдете корисну інформацію про аутентифікацію на основі токенів.

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 для аутентифікації без стану.
кассіомолін

А що з надсиланням хешованого пароля замість простого (хеш-файлу з генерованим сервером nonce)? Це підвищує рівень безпеки (наприклад, коли не використовується https)? У випадку з людиною посередині - він зможе викрасти один сеанс, але принаймні пароль не отримає
Денис Іцкович

15
Я не можу повірити, що це не в офіційній документації.
Даніель М.

2
@grep У REST немає такого поняття, як сеанс на стороні сервера. Отже, стан сеансу керується на стороні клієнта.
кассіомолін

98

Ця відповідь стосується авторизації, і вона є доповненням моєї попередньої відповіді про автентифікацію

Чому інша відповідь? Я спробував розширити свою попередню відповідь, додавши подробиці про підтримку анотацій JSR-250. Однак оригінальна відповідь стала занадто довгою і перевищила максимальну довжину 30000 символів . Тому я перемістив цілі деталі авторизації на цю відповідь, тримаючи іншу відповідь зосередженою на виконанні автентифікації та видачі жетонів.


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


Чи є сховище github з цим елегантним рішенням?
Даніель Феррейра Кастро

6
@DanielFerreiraCastro Звичайно. Подивіться тут .
кассіомолін

Чи є якийсь хороший спосіб підтвердити, що запит надходить від авторизованого користувача І що користувач може змінити дані, оскільки він "володіє" даними (наприклад, так що хакер не може використовувати маркер для зміни імені іншого користувача)? Я знаю, що можу перевірити в кожній кінцевій точці, чи user_id== token.userId, або щось подібне, але це дуже повторюється.
mFeinstein

@mFeinstein Відповідь на це, безумовно, вимагатиме більше символів, ніж я можу набрати тут у коментарях. Для того, щоб дати вам певний напрямок, ви можете шукати рівень безпеки рядків .
кассіомолін

Я бачу безліч тем у базах даних, коли шукаю безпеку рівня рядків, тоді відкрию це як нове запитання
mFeinstein
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.