Пользовательский контроль доступа к конечным точкам REST в приложении Spring Boot - PullRequest
0 голосов
/ 10 июня 2018

Прежде всего, я знаю о @PreAuthorize аннотациях и о Контроль доступа на основе выражений .

Ради обучения (а также по многим причинам), что бы я хотелхотелось бы иметь следующее:

  • Пользователи проходят проверку подлинности, а их роли предоставляются каталогом LDAP и заполняются в объекте Principal при аутентификации.Это работает, как в случае «оно в настоящее время используется в проекте».
  • Аннотация (выбрана @AccessControl) реализует парадигму, согласно которой управление доступом полностью связано с ролями.Аннотация может быть установлена ​​для класса / типа (контроллер REST), и в этом случае она применяется к любому методу, для которого нет другой такой аннотации, или к методу (конечная точка REST).Самая глубокая аннотация всегда побеждает, независимо от того, ограничивает она или ослабляет ограничение авторизации.
  • Логика управления доступом, которая немного сложнее, чем та, которую я мог бы получить из управления доступом на основе выражений, была бы применена другимкусок кода.Это также немного более легко обслуживаемо, но я предполагаю, что это только в моих глазах.

В качестве примера контроллер мог бы иметь, кроме аннотации @AccessControl для метода, конечные точки, которые могут толькобыть доступными для пользователей с ADMIN в их списке ролей:

@RestController
@RequestMapping("/admin")
@AccessControl({ Roles.ADMIN })
public class AdminController {
...
}

Моя текущая нерешительность, после прочтения в эти последние дни больше о том, писать ли пользовательский фильтр запросов или скорее совет AOP.

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

С помощью AOP я не знаю (пока), как ответить клиенту со статусом 403 Forbidden.

Мои вопросы вытекают непосредственно изэти две точки:

  • Как я могу получить метод контроллера, который будет вызываться для запроса клиента?
  • Как я могу вернуть код состояния HTTP из рекомендации AOP и эффективно завершить обработку запроса, когда клиент не авторизован?

Ответы [ 2 ]

0 голосов
/ 16 июня 2018

Оказалось, что все намного проще, чем я первоначально думал, и я завершил его менее чем за день, используя опцию AOP.

Это код аннотации AccessControl, комментарии удалены:

@Documented
@Inherited
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
public @interface AccessControl {

    public String[] value() default {};

}

Его можно разместить либо на контроллере (см. Мой оригинальный пост / вопрос), либо на методе контроллера:

@RestController
@RequestMapping("/admin")
@AccessControl({ Roles.ADMIN })
public class AdminController {


    // This endpoint has open access: no authorization check will happen.
    @AccessControl
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    public DummyDto getNoCheck(@PathVariable Integer id) {

        return service.get(id);
    }

    // This endpoint specifically allows access to the "USER" role, which is lower 
    // than ADMIN in my hierarchy of roles.
    @AccessControl(Roles.USER)
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    public DummyDto getCheckUser(@PathVariable Integer id) {

        return service.get(id);
    }

    // The authorization check defaults to checking the "ADMIN" role, because there's
    // no @AccessControl annotation here.
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    public DummyDto getCheckRoleAdmin(@PathVariable Integer id) {

        return service.get(id);
    }

}

Чтобы выполнить фактическую проверку, необходимо два вопросаответить:

  • во-первых, какие методы должны быть обработаны?
  • во-вторых, что проверяется?

Вопрос 1: какие методыдолжны быть обработаны?

Для меня ответ был что-то вроде "все конечные точки REST в моем коде".Поскольку мой код находится в определенном корневом пакете, и поскольку я использую аннотацию RequestMapping в Spring, конкретный ответ приходит в форме спецификации Pointcut:

@Pointcut("execution(@org.springframework.web.bind.annotation.RequestMapping * *(..)) && within(my.package..*)")

Вопрос 2: чтоточно проверяется во время выполнения?

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

@Around("accessControlled()")
public Object process(ProceedingJoinPoint pjp) throws Throwable {
    ...
    // Get the roles specified in the access control rule that applies (from the method annotation, or from the controller annotation).
    // Get the user roles from the UserDetails previously saved when the user went through the authentication process.
    // Check authorizations: does the user have one role that is required? If no, throw an exception. If yes, don't do anything.
    // No exception has been thrown: let the method proceed and return its results.
}

То, что беспокоило меня в моем первоначальном мышлении, было исключением.Поскольку у меня уже был класс сопоставления исключений, который содержит аннотацию @ControllerAdvice, я просто повторно использовал этот класс для сопоставления моего конкретного AccessControlException с кодом статуса 403 Запрещенным.

Для получения ролей пользователя я использовал SecurityContextHolder.getContext().getAuthentication() для восстановления токена аутентификации, затем authentication.getPrincipal() для извлечения пользовательского объекта сведений о пользователе, который имеет поле roles, которое я обычно настраиваю в процессе аутентификации.

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

0 голосов
/ 11 июня 2018

Я хочу предоставить подход, который вы можете использовать, если хотите следовать корню рекомендаций AOP:

По этому вопросу при использовании AOP:

Как я могу вернуть HTTPкод статуса из AOP совета и эффективно завершить обработку запроса, когда клиент не авторизован? решение:

В вашем классе аспектов, используя в Around Advice, сделайте следующее:

@Around("execution(* net.my.package.AdminController.*(..)) && args(.., principal)")
public ResponseEntity<?> processRequest(final ProceedingJoinPoint joinPoint, final Principal principal) {
    final String controllerMethodName = joinPoint.getSignature().getName();
    LOGGER.info("Controller Method name : {}", controllerMethodName);
    final boolean isAuthSuccessful = authenticationService.authenticate(principal);//Pass auth details here
    if(!isAuthSuccessful) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Request declined"); //End request if auth failed
    } else {
        try {
            return (ResponseEntity<?>)joinPoint.proceed(); //Continue with request
        } catch (Throwable e) {
            LOGGER.error("Error In Aspect :", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("failed request");
        }
    }
}

Что ж, в приведенном выше коде есть комментарии по решению проблемвы сталкиваетесь.Но для того, чтобы этот код работал, убедитесь, что выполните следующее:

  1. Убедитесь, что все методы вашего контроллера, которые вы хотите перехватить, возвращают ResponseEntity
  2. Вы можете изменить аспект @Around, чтобы использоватьВырезать точку со значением @annotation для вашей аннотации @AccessControl, и вы можете связать условия в аспекте @Around
  3. Убедитесь, что вы нашли способ передать данные в этот аспект, чтобы у вас был способпроверить учетные данные пользователя
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...