Spring Security запрещает доступ к методу `@ Secured`, несмотря на предоставленные полномочия - PullRequest
2 голосов
/ 06 июня 2019

Итак, у меня есть представление Vaadin (8), которое я @Secure d для Spring Security:

@Secured(SUPERADMIN_ROLE)
@SpringView(name = AdminHomeView.NAME)
class AdminHomeView : DemoViewWithLabel(){
    companion object {
        const val NAME = "admin/home"
    }

    override val labelContent = "This is the protected admin section. You are authenticated and authorized."
}

где DemoViewWithLabel - это очень простой абстрактный класс, отображающий

VerticalLayout(Label(labelContent))

Так что, если я войду как кто-то с ролью Superadmin, я смогу получить доступ к этому представлению просто отлично.

Однако давайте сделаем небольшое изменение и переопределим метод ...

@Secured(SUPERADMIN_ROLE)
@SpringView(name = AdminHomeView.NAME)
class AdminHomeView : DemoViewWithLabel(){
    companion object {
        const val NAME = "admin/home"
    }

    override val labelContent = "This is the protected admin section. You are authenticated and authorized."

    override fun enter(event: ViewChangeListener.ViewChangeEvent?) {
        super.enter(event)
    }
}

ЭТО дает мне AccessDeniedException ... и я не понимаю почему.

Итак, я включил отладочный логин для Spring Security, и вот что он должен был сказать:

Secure object: ReflectiveMethodInvocation: public void ch.cypherk.myapp.ui.views.admin.AdminHomeView.enter(com.vaadin.navigator.ViewChangeListener$ViewChangeEvent);
  target is of class [ch.cypherk.myapp.ui.views.admin.AdminHomeView];
  Attributes: [Superadmin]
Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@a6801701:
  Principal: ch.cypherk.myapp.model.auth.MyUserDetails@6e4c5c5b;
  Credentials: [PROTECTED];
  Authenticated: true; Details: null;
  Granted Authorities: RIGHT_MANAGER, Superadmin 

Пока все в порядке. Он ожидает полномочия Superadmin и имеет аутентифицированного пользователя с этим Superadmin полномочием.

Однако ...

Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@7ddc2dd1, returned: 0  
Voter: org.springframework.security.access.vote.RoleVoter@75d3fbb7, returned: 0  
Voter: org.springframework.security.access.vote.AuthenticatedVoter@391740fc, returned: 0

и, следовательно,

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AbstractAccessDecisionManager.checkAllowIfAllAbstainDecisions(AbstractAccessDecisionManager.java:70)
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:89)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
    at ch.cypherk.myapp.ui.views.admin.AdminHomeView$$EnhancerBySpringCGLIB$$ae16a97c_4.enter(<generated>)
    at com.vaadin.navigator.Navigator.performNavigateTo(Navigator.java:778)
    at com.vaadin.navigator.Navigator.lambda$navigateTo$9a874efd$1(Navigator.java:702)
    at com.vaadin.navigator.ViewBeforeLeaveEvent.navigate(ViewBeforeLeaveEvent.java:54)
    at com.vaadin.navigator.View.beforeLeave(View.java:79)
    at com.vaadin.navigator.Navigator.runAfterLeaveConfirmation(Navigator.java:730)
    at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:701)
  at ...

ПОЧЕМУ? !!

Буду признателен за помощь в решении этой проблемы.

1 Ответ

1 голос
/ 06 июня 2019

В двух словах

Предоставленные полномочия не демонстрируют префикс ROLE_ и поэтому игнорируются RoleVoter.

Замена RoleVoter с пользовательской реализацией, которая имеет пустой префикс, решает проблему.

Полная история

Теперь остается вопрос, почему мы вообще можем получить доступ к представлению.Объяснение простое, но требует немного большего контекста.

Мы работаем над переносом приложения Thorntail на Spring Boot.

И мы используем Vaadin 8 (потому что у нас есть устаревший Vaadin 7вещи, от которых нам пока не удалось избавиться и которые необходимо поддерживать).

И так?

Vaadin - это одностраничный фреймворк, а в Vaadin 8 есть такой неприятный способ ссылки на представления.а именно, он использует #! в корневом URL-адресе (например, https://<host>:<port>/#!foo/bar/baz/...).

Ничего после того, как # не отправлено на сервер, это означает, что мы не можем различить доступ к / идоступ к /#!foo/bar/baz/...

Таким образом, мы не можем использовать Spring Security для защиты доступа к представлениям.

И у нас есть представление Vaadin, к которому нам нужно разрешить доступ без аутентификации.

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

Доступ к самим представлениям защищен Vaadin, а не Spring.SpringViewProvider не найдет View, к которому пользователь не должен иметь доступ (как следствие, пользователь не сможет там перемещаться).

Однако, как только мы вызовем метод в этом представлении, Spring Spring активирует и выполняет проверки доступа. эти проверки завершились неудачно, поскольку предоставленные полномочия не имели префикса "ROLE _", ожидаемого Spring.(Я предполагал, что это просто соглашение, но это не так; RoleVoter фактически игнорирует полномочия, которые не показывают префикс, поэтому вы ДОЛЖНЫ сделать это таким образом, ИЛИ вы должны предоставить свой RoleVoter.)

Решение было относительно простым ...

@Configuration
@EnableGlobalMethodSecurity(
    securedEnabled = true,
    prePostEnabled = true,
    proxyTargetClass = true
)
class GlobalSecurityConfiguration : GlobalMethodSecurityConfiguration(){
    override fun accessDecisionManager(): AccessDecisionManager {
        return (super.accessDecisionManager() as AffirmativeBased).apply {
            decisionVoters.replaceAll {
                when(it){
                    is RoleVoter -> MyRoleVoter()
                    else -> it
                }
            }
        }
    }
}

, где

class MyRoleVoter : RoleVoter(){
    init {
        rolePrefix = ""
    }
}
...