Search code examples
springsecurity

Spring Boot 3 + Spring Security 6 => 403 Forbidden with "requestMatchers"


For several days now I have been trying to solve a problem with Spring Security 6. I've read almost all the spring documentation for Spring Security 6 and I watched several tutorials and just cannot see where the mistake is. I looked at the code under a magnifying glass:

WebSecurityConfigurer.class:

package com.transfer.market.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer {

    private final String ADMIN;
    private final String ADMIN_PASS;
    private final String SUPER;
    private final String SUPER_PASS;

    @Autowired
    public WebSecurityConfigurer(AppSecurityExternalConfig appSecurityExternalConfig) {
        this.ADMIN = appSecurityExternalConfig.getUser().getAdmin();
        this.ADMIN_PASS = appSecurityExternalConfig.getPassword().getAdmin();
        this.SUPER = appSecurityExternalConfig.getUser().getSup();
        this.SUPER_PASS = appSecurityExternalConfig.getPassword().getSup();
    }

    @Bean
    public UserDetailsService users() {

        UserDetails admin = User.builder()
                .username(ADMIN)
                .password(encoder().encode(ADMIN_PASS))
                .roles("ADMIN")
                .build();

        UserDetails sup = User.builder()
                .username(SUPER)
                .password(encoder().encode(SUPER_PASS))
                .roles("ADMIN", "DBA")
                .build();

        return new InMemoryUserDetailsManager(admin, sup);
    }

    @Bean
    public SecurityFilterChain web(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeHttpRequests(auth -> auth
                                .requestMatchers("/resource/**").permitAll()
                                .requestMatchers("/api/**").hasAnyRole("ADMIN", "DBA")
                                .requestMatchers("/db/**")
            .access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))
                                .anyRequest().denyAll()
                );


        return http.build();
    }

    @Bean
    public static BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
}

I have printed ADMIN, SUPER and their passwords to the console and they are for sure read correctly from the application.properties. So that is not the problem.

AppSecurityExternalConfig.class:

package com.transfer.market.configuration;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Data
@Configuration
@ConfigurationProperties(prefix = "config.security")
public class AppSecurityExternalConfig {
    private User user;
    private Password password;

    @Data
    @Component
    @ConfigurationProperties(prefix = "user")
    public static class User {
        private String user;
        private String admin;
        private String sup;
    }

    @Data
    @Component
    @ConfigurationProperties(prefix = "password")
    public static class Password {
        private String user;
        private String admin;
        private String sup;
    }
}

application.properties:

...
# Security:
config.security.user.admin=admin
config.security.password.admin=pass

config.security.user.sup=super
config.security.password.sup=pass
...

PlayerController.class:

@RestController
@Validated
public class PlayerController {

    private final PlayerService playerService;

    @Autowired
    public PlayerController(PlayerService playerService) {
        this.playerService = playerService;
    }

    @PostMapping("/api/players")
    public ResponseEntity<Player> addSingle(@RequestBody @Valid Player player) {
        return new ResponseEntity<>(playerService.addSingle(player), HttpStatus.CREATED);
    }
...

enter image description here enter image description here

It just keeps getting "403 Forbidden", but for all end points starting with "/resource" where they are .permitAll() it works and it's 200 OK. Why doesnt the other requestMatchers work? Please help.


Solution

  • @Bean
    public SecurityFilterChain web(HttpSecurity http) throws Exception {
      http.csrf().disable()
          .authorizeHttpRequests(auth -> auth.requestMatchers("/resource/**").permitAll()
              .requestMatchers("/api/**")
              .hasAnyRole("ADMIN", "DBA")
              .requestMatchers("/db/**")
              .access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and
    hasRole('DBA')"))
              .anyRequest().denyAll());
      return http.build();
    }
    

    You have to configured the basic authentication. Add the following statement in the SecurityFilterChain bean.

    http.httpBasic();
    

    i.e.

    @Bean
    public SecurityFilterChain web(HttpSecurity http) throws Exception {
      http.csrf().disable()
              .authorizeHttpRequests(auth -> auth.requestMatchers("/resource/**").permitAll()
              .requestMatchers("/api/**")
              .hasAnyRole("ADMIN")
              .requestMatchers("/db/**")
              .access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))
              .anyRequest().denyAll());
      http.httpBasic();
      return http.build();
    }
    

    enter image description here