Spring Security denies access to `@Secured` method in spite of granted authorities

User1291 :

So I have a Vaadin (8) view that I've @Secured for 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."
}

where DemoViewWithLabel is just a very simple abstract class displaying

VerticalLayout(Label(labelContent))

So if I log in as somebody with the Superadmin role, I can access this view just fine.

However, let's make a minor change and override a method ...

@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)
    }
}

THIS gets me an AccessDeniedException ... and I don't understand why.

So I turned on debug loggign for Spring Security, and this is what it had to say:

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 

This seems ok, so far. It expects a Superadmin authority and it has an authenticated user with that Superadmin authority.

However ...

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

and consequently,

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 ...

WHY?!!

Would appreciate some help in resolving this.

User1291 :

In a Nutshell

The authorities granted do not exhibit the ROLE_-prefix and therefore get ignored by the RoleVoter.

Replacing the RoleVoter with a custom implementation that has an empty prefix solved the issue.

Full Story

Now this leaves the question why we could access the view at all. The explanation is simple but requires a bit more context.

We're working on migrating a Thorntail application to Spring Boot.

And we're using Vaadin 8 (because we have legacy Vaadin 7 stuff that we haven't managed to get rid of yet and need to support).

And so?

Vaadin is a single-page framework and Vaadin 8 has this nasty way of referencing views, namely, it uses a #! on the root url (e.g. https://<host>:<port>/#!foo/bar/baz/...).

Nothing after # is sent to the server, which means we cannot differentiate between an access to / and an access to /#!foo/bar/baz/...

As such, we cannot use Spring Security to guard accesses to views.

AND we have a Vaadin view that we need to allow unauthenticated access to.

As a consequence, we were forced to allow unauthenticated accesses to /. The MainUI (that handles all the incoming requests) would check if the user is authenticated and redirect to a login page if they were not.

Access to the views themselves is guarded by Vaadin, not Spring. The SpringViewProvider will not find a View to which a user is not supposed to have access to (as a consequence, the user cannot navigate there).

However, as soon as we call a method on that view, Spring security kicks in and performs the access checks. It is these checks that failed because the authorites granted did not have the "ROLE_"-prefix Spring expected. (I had assumed this was just a convention, but it's not; the RoleVoter actually ignores authorities that do not exhibit the prefix, so you HAVE to do it that way, OR you have to provide your own RoleVoter.)

The solution was relatively straightforward...

@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
                }
            }
        }
    }
}

where

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=156293&siteId=1