spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.71k stars 5.85k forks source link

Document how to upgrade to v6 with ACL #12995

Open ChristianSch opened 1 year ago

ChristianSch commented 1 year ago

Hi,

I think the documentation is lacking crucial information on how to proceed with ACL in spring-security-6. The aforementioned docs list either AccessDecisionVoter or AfterInvocationProvider to be implemented, both of which are deprecated though. The migration docs are of no help and the usual more helpful resource SO is not up to speed yet or might never be.

The ACL samples also got no update and I don't think they would actually just work out of the box, as all that's been done for the update was to bump versions.

To give more context to my personal problem, I implement a custom permission evaluator but neither AccessDecisionManager or AccessDecisionVoter.

    @Bean
    fun expressionHandler(): MethodSecurityExpressionHandler {
        val customPermissionEvaluator = CustomPermissionEvaluator(aclService())
        customPermissionEvaluator.setPermissionFactory(permissionFactory())

        val expressionHandler = DefaultMethodSecurityExpressionHandler()
        expressionHandler.setPermissionEvaluator(customPermissionEvaluator)

        return expressionHandler
    }

Other than that we populate some grants in an AuthenticationEventListener but nothing else. Here's the relevant filter chain (I updated to the DSL just now and really dig it!):

@Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        val customConverter = JwtAuthenticationConverter()
        customConverter.setJwtGrantedAuthoritiesConverter(
            KeycloakPermissionsConverter(workspaceName, permissionsClaimKey)
        )

        http {
            authorizeRequests {
                // anyone with a valid jwt can access endpoints
                // the token is validated by the custom jwt converter set below
                authorize(anyRequest, authenticated)
                authorize(GET, "/actuator", permitAll)
                authorize(GET, "/actuator/*", permitAll)
                authorize(GET, "/api-docs", permitAll)
                authorize(GET, "/api-docs.yaml", permitAll)
                // the following endpoint is secured by custom tokens and not keycloak
                // TODO: could also be handled by spring-security probably
                authorize(POST, "/api/v1/public/redacted/callbacks", permitAll)
            }

            // disable unused stuff
            anonymous { disable() }
            logout { disable() }

            // disable X-FRAME-OPTIONS: deny, especially for file downloads. has virtually no effect on other routes
            headers { frameOptions { disable() } }

            // enable cors
            cors { }

            // places custom filter *before* the anonymous auth filter in the spring security
            // filter chain ordering (see addFilter)
            addFilterBefore<BearerTokenAuthenticationFilter>(filterChainExceptionHandler)
            addFilterAt<BearerTokenAuthenticationFilter>(queryParameterTokenFilter)

            // configure tokens to be validated and converted to our internal authorization with workspace READ/ADMIN permissions
            oauth2ResourceServer { jwt { jwtAuthenticationConverter = customConverter } }
        }

        return http.build()
    }

combined with:

@Configuration
@EnableMethodSecurity

I feel that there is some magic glue missing from samples and/or docs to re-enable a perfectly working app after upgrading.

18:33:43.735 [Test worker] DEBUG org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor - Authorizing method invocation ReflectiveMethodInvocation: public redacted redactedService.findById(java.lang.Long) throws redactedNotFoundException,eu.do24.web.api.errors.UnauthorizedException,org.springframework.security.access.AccessDeniedException; target is of class [redactedService]
18:33:43.740 [Test worker] WARN  org.springframework.security.access.expression.DenyAllPermissionEvaluator - Denying user cd9f1f41-07f5-4cc1-be00-b871e6ca8c78 permission 'READ' on object redacted@1a0373a4

PS: if you're reading this later, please be advised that for all the permitted routes we either filter external traffic for that!

ChristianSch commented 1 year ago

Related https://github.com/spring-projects/spring-security/issues/13088