ons1719133
ons1719133

Reputation: 315

Spring security requestMatchers with hasAnyAuthority or hasAnyRole not works as expected

In my Spring boot application, I am using the below version in pom.xml. Java version is 17.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.4</version>
    <relativePath/> 
</parent>

My controller class,

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/common")
public class CommonController {

  @GetMapping("/dashboard")
  @PreAuthorize("hasRole('ROLE_MODERATOR') or hasRole('ROLE_ADMIN')")
  public String userAccess() {
    return "Dash Board Sample.";
  }
}

Although I am using @PreAuthorize at the method level, I want to limit access for the url pattern /api/common/** to allow only users having the roles "ROLE_USER" or "ROLE_MODERATOR" or "ROLE_ADMIN". But it is not working and giving me 401 when using hasAnyAuthority or hasAnyRole in the filter chain.

Here is my entire SecurityConfig class and I am using the filter chain as follows.

import com.example.authservicedemo.security.jwt.AuthTokenFilter;
import com.example.authservicedemo.security.services.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

    @Value("${spring.security.debug:false}")
    boolean securityDebug;

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

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

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeHttpRequests()
                .requestMatchers("/login/**")
                .anonymous()
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/test/**").permitAll()
                //.requestMatchers("/api/common/**").permitAll()
                .requestMatchers("/api/common/**").hasAnyAuthority("ROLE_USER", "ROLE_MODERATOR", "ROLE_ADMIN")
                 //.requestMatchers("/api/common/**") .hasAnyRole("USER", "MODERATOR", "ADMIN")
                .anyRequest()
                .authenticated()
                .and()
                .httpBasic()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.debug(securityDebug)
                .ignoring()
                .requestMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
    }
}

But it gives me 401 error when I access the API.

    curl --location 'http://localhost:8080/api/common/dashboard' \
--header 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTY5MTAzNDk4OSwiZXhwIjoxNjkxMTIxMzg5LCJyb2xlcyI6WyJQXzEiLCJQXzMiLCJBXzEiLCJBXzIiLCJBXzMiLCJBXzQiLCJBXzUiLCJST0xFX0FETUlOIl19.8Fp6OPBMsredmLEi2RdB0Bkg0Oa1S_k869mx25CJxEjD01ePys7J7E7mWHRmeeKFMX_ybeDpdK_5loT26eBMAQ'

When I uncomment the line .requestMatchers("/api/common/").permitAll()** this works well with the method level security @PreAuthorize. So, how can I limit access to specific URL patterns for users with specific roles?

I cannot figure out what I am doing wrong. Your response would be really appreciated.

Upvotes: 2

Views: 4256

Answers (2)

Akul96
Akul96

Reputation: 11

This is not a complete answer to your question, but check my config. Also you can both have endpoint security and method level security, it is not an issue.

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
    
        http
            .userDetailsService(jpaUserDetailsService)
            .authorizeHttpRequests(auth -> auth
                            .requestMatchers("/h2-console/**").permitAll()
                            .requestMatchers( "/auth/**").permitAll()
                            .requestMatchers(HttpMethod.POST, 
                                "/auth/**").permitAll()
                            .anyRequest().authenticated()
            )
            .csrf(AbstractHttpConfigurer::disable) //to be able to use h2- 
                //console and different domains with the login
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .formLogin(Customizer.withDefaults())
            .logout(logout -> logout
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .logoutSuccessUrl("/auth/login")
            )
            .headers(headers -> headers 
                 .frameOptions(FrameOptionsConfig::disable)) //to be able to use 
                   //h2-console
            .sessionManagement((sessionManagement) -> sessionManagement
                 .sessionConcurrency((sessionConcurrency) ->
                     sessionConcurrency
                         //.maximumSessions(1)
                         //.maxSessionsPreventsLogin(true)
                         .expiredUrl("/auth/login?invalid-session=true")
                         .sessionRegistry(sessionRegistry())
                 )
                 // Set the custom session repository here
                 //.sessionRepository(customSessionRepository()) // Inject your 
                    //custom session repository
            );

            //http.authenticationProvider(authProvider());
            
    return  http.build();
}

And here is an example if you want to secure an endpoint based on roles, it is better to use it in the class:

@RestController
@RequestMapping("/manage-users")
@PreAuthorize("hasAuthority('ADMIN')")
public class UsersController{
     //some code
}

And for method level:

@GetMapping("/new")
@PreAuthorize("hasAuthority('ADMIN')")
public String showRegistrationForm() {
    return "registrationForm";
}

Upvotes: 1

Sangam Belose
Sangam Belose

Reputation: 4506

You are using both method-level security annotations (@PreAuthorize) and URL-based security rules. If you are using method-level security (@preAuthorize), the security checks are performed based on the expression specified in the annotation, and URL-based security rules defined in SecurityConfig may not have any effect on those methods.

Also the method level security is recommended to be applied in Service layer. spring security docs

For your security config try using antmatchers as below:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable()
            .authorizeRequests()
            .antMatchers("/login/**").anonymous()
            .antMatchers("/api/auth/**").permitAll()
            .antMatchers("/api/test/**").permitAll()
            .antMatchers("/api/common/**").hasAnyAuthority("ROLE_USER", "ROLE_MODERATOR", "ROLE_ADMIN")
            .anyRequest().authenticated()
            .and()
            .httpBasic()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    return http.build();
}

Upvotes: 3

Related Questions