Search code examples
javaspring-bootspring-security

All roles unable to access a protected endpoint


I am trying to secure an endpoint using Spring Security. I have created two roles, ADMIN and CUSTOMER, and for testing purposes I am trying to secure a particular endpoint /api/v1/customers/ so that only ADMIN can access it.

As it turns out none of the roles, even ADMIN is also unable to access it. I realised I previously wasn't using api/v1/customers and just using /customers/ so I fixed that.

Also based on an answer regarding a similar issue I have entered same end point twice, with and without slash in the end (check the config code, you'll know).

Nothing seems to work, I am sharing my SecurityConfig and controller (only useful parts) classes below.

CustomerController.java

@CrossOrigin(origins = "http://localhost:3000/")
@RestController
@RequestMapping("api/v1")
public class CustomerController {
    
    @Autowired
    private CustomerRepository customerRepository;

    @RequestMapping(value = "/customers", method = RequestMethod.GET)
    public List<Customer> getAllCustomers(){
        return customerRepository.findAll();
    }
}

SecurityConfiguration.java

@Configuration
@EnableWebSecurity
public class SecurityConfiguration{
    
    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        // disabling the csrf isn't really the best way of dealing with this, need to fix !!!
        http
            .csrf().disable();
        http
            .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/api/v1/customers/", "/api/v1/customers").hasRole("ADMIN")
                .anyRequest().authenticated().
                .and()
            .formLogin();
        return http.build();
    }

    @Bean
    protected InMemoryUserDetailsManager configureAuthentication(){

        List<UserDetails> userDetails = new ArrayList<>();
        List<GrantedAuthority> customerRoles = new ArrayList<>();
        customerRoles.add(new SimpleGrantedAuthority("CUSTOMER"));
        List<GrantedAuthority> adminRoles = new ArrayList<>();
        adminRoles.add(new SimpleGrantedAuthority("ADMIN"));

        userDetails.add(new User("user_customer", this.PasswordEncoder().encode("password"), customerRoles));
        userDetails.add(new User("user_admin", this.PasswordEncoder().encode("password"), adminRoles));
        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean
    public PasswordEncoder PasswordEncoder(){
        return new BCryptPasswordEncoder(10);
    }
}

Solution

  • As stated in javadoc here and here, SecurityExpressionRoot's .hasRole() and .hasAnyRole() methods add "ROLE_" prefix to passed arguments by default, if you did not change this default GrantedAuthority prefix.
    So, with .hasRole("ADMIN") spring-security will try to match actual authority name with "ROLE_ADMIN" instead of just "ADMIN".

    But your in-memory User has a SimpleGrantedAuthority with "ADMIN" authority, so you get 403 status as a result.

    You can fix it in 3 ways:

    1. save roles as SimpleGrantedAuthority("ROLE_ADMIN") and SimpleGrantedAuthority("ROLE_CUSTOMER") (with prefix)
    2. use .hasAuthority() instead of .hasRole() method - the first one doesn't change passed argument and doesn't add any prefixes.
    3. override default prefix like this:
        @Bean
        public GrantedAuthorityDefaults grantedAuthorityDefaults() {
            return new GrantedAuthorityDefaults(""); // <- no default prefix
        }