0

I'm currently working on avoid literals inside the security annotations. The reason is that in large applications having literals inside these annotations rely on several errors, also refactoring permissions names will be more easy.

I created a @Bean it's purpose is register the Enum packages that I want to then use in the @Preautorize annotations in this way @PreAuthorize("hasAuthority(T(Permissions).CREATE)")

@Bean
    static MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        return new DefaultMethodSecurityExpressionHandler() {
            @Override
            public StandardEvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
                                                                     MethodInvocation mi) {

                StandardEvaluationContext evaluationContext =
                        (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
                ((StandardTypeLocator) evaluationContext.getTypeLocator())
                        .registerImport(PermissionName.class.getPackage().getName());

                return evaluationContext;
            }
        };
    }

but the functions T() can't resolve the class Permissions.

About the technical information I'm working on Spring Boot 3.2.4.

If you need more information or discuss about it fell free! Thanks for your help!

2
  • 1
    The Spring Security documentation recommends your custom MethodSecurityExpressionHandler be produced by a static @Bean method to ensure that Spring publishes the bean before it initializes Spring Security’s method security @Configuration classes. You produce the bean by the @Component annotation which does not satisfy this recommendation.
    – Chin Huang
    Apr 16 at 6:04
  • Thanks @ChinHuang. I edited my questions with your advice but It does not work. There is any way to access the StandardEvaluationContext and make changes on it without Override the createEvaluationContext method? Thanks a lot for your help.
    – naumb
    Apr 16 at 8:32

1 Answer 1

0

This should work:

Security config with a custom MethodSecurityExpressionHandler; note: in your code you use PermissionName instead of Permissions

@Configuration
@EnableMethodSecurity // <-- important
public class SecurityConfig {
    
    @Bean
    static MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        return new DefaultMethodSecurityExpressionHandler() {
            @Override
            public StandardEvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
                                                                     MethodInvocation mi) {

                StandardEvaluationContext evaluationContext =
                        (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
                ((StandardTypeLocator) evaluationContext.getTypeLocator())
                        .registerImport(Permissions.class.getPackage().getName());

                return evaluationContext;
            }
        };
    }
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        return httpSecurity.authorizeHttpRequests(
                        auth -> {
                            auth.anyRequest().authenticated();
                        }
                )
                .formLogin(withDefaults())
                .build();
    }
    
    /* user with permission READ */
    /* admin with permissions CREATE and READ */
    @Bean
    public UserDetailsService users() {
        // The builder will ensure the passwords are encoded before saving in memory
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        UserDetails user = users
                .username("user")
                .password("password")
                .roles("USER")
                .authorities("READ") // add some authorities
                .build();
        UserDetails admin = users
                .username("admin")
                .password("password")
                .roles("USER", "ADMIN")
                .authorities("READ","CREATE") // add some authorities
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}

The Permissions class with public accessible fields

public class Permissions {

    public static final String CREATE = "CREATE";
    public static final String READ = "READ";

    /* possible easier solution */
    public static final String CREATE_AUTH = "hasAuthority('CREATE')";
}

Usage in a Controller

@RestController
public class DemoController {

    @GetMapping("/read")
    @PreAuthorize("hasAnyAuthority(T(Permissions).READ)")
    public String read(@AuthenticationPrincipal UserDetails user) {
        return "Hello reader: %s %s".formatted(user.getUsername(), user.getAuthorities());
    }

    @GetMapping("/create")
    @PreAuthorize("hasAnyAuthority(T(Permissions).CREATE)")
    public String create(@AuthenticationPrincipal UserDetails user) {
        return "Hello creator: %s %s".formatted(user.getUsername(), user.getAuthorities());
    }

    @GetMapping("/other")
    @PreAuthorize(Permissions.CREATE_AUTH) // maybe an easier solution?
    public String other(@AuthenticationPrincipal UserDetails user) {
        return "Hello creator: %s %s".formatted(user.getUsername(), user.getAuthorities());
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.