Issue
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:
- save roles as
SimpleGrantedAuthority("ROLE_ADMIN")
andSimpleGrantedAuthority("ROLE_CUSTOMER")
(with prefix) - use
.hasAuthority()
instead of.hasRole()
method - the first one doesn't change passed argument and doesn't add any prefixes. - override default prefix like this:
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // <- no default prefix
}
Answered By - AndrewThomas
Answer Checked By - Cary Denson (JavaFixing Admin)