Home > Software engineering >  What is wrong in this Spring Security configuration? How can I make an endpoint accessible to users
What is wrong in this Spring Security configuration? How can I make an endpoint accessible to users

Time:02-14

I am working on a Spring Boot protecting my APIs using Spring Security and JWT tokens and I have the following iusse.

I have an API handling endpoint like this: http://localhost:8019/api/admin/user/54/wallet

I have 2 user types:

  • ADMIN USER: having ADMIN autority defined into the JWT tokend.
  • CLIENT USER: having CLIENT autority defined into the JWT tokend.

The previous API must be accessible by both the user types (I know that the /admin/ section in the URI is not the best...it will be refactored in the near future).

Then I have this class extending the Spring Boot WebSecurityConfigurerAdapter class and implementing my security configuration:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    @Autowired
    @Qualifier("customUserDetailsService")
    private UserDetailsService userDetailsService;
    
    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    
    private static final String[] USER_MATCHER = { "/api/user/email/**"};

    
    private static final String[] CLIENT_MATCHER = { 

                                                        "/api/users/email/*",
                                                        //"/api/admin/**",
                                                        "/api/admin/user/{userID}/wallet"
                                                    };
    
    private static final String[] ADMIN_MATCHER = { 
                                                        "/api/users/email/**",
                                                        "/api/admin/users",
                                                        "/api/admin/user/{userID}/wallet",
                                                        "/api/admin/**"
                                                        
                                                   };
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
    {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
    {
        return super.authenticationManagerBean();
    }   
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /*
         * NOTE:
         * Using hasRole expects the authority names to start with 'ROLE_' prefix
         * Instead, we use hasAuthority as we can use the names as it is
         */
        http.csrf().disable()
                   .authorizeRequests()

                   .antMatchers(USER_MATCHER).hasAnyAuthority("USER")
                   .antMatchers(CLIENT_MATCHER).hasAnyAuthority("CLIENT")
                   .antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN")
                   //.antMatchers(CLIENT_MATCHER).hasAnyAuthority("CLIENT")
                   .antMatchers("/api/users/test").authenticated()
                   .antMatchers(HttpMethod.POST, jwtConfig.getUri()).permitAll()
                   .anyRequest().denyAll()
                   .and()
                   .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(
            new TokenVerificationFilter(authenticationManager(), jwtConfig, jwtTokenUtil),UsernamePasswordAuthenticationFilter.class);
    }
    
    /* To allow Pre-flight [OPTIONS] request from browser */
    @Override
    public void configure(WebSecurity web) 
    {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
        web.ignoring().antMatchers("/swagger-ui/**",
                                    "/webjars/**",
                                    "/v2/**",
                                    "/swagger-resources/**",
                                    "/swagger-ui.html");
    }

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


}

The problem is that as it is defined in the previous configuration if I try to call the previous API (http://localhost:8019/api/admin/user/54/wallet) with a token related to an user having the CLIENT autority I am obtaining this error message:

{
    "timestamp": "2022-02-13T20:22:10.418 00:00",
    "status": 403,
    "error": "Forbidden",
    "message": "Forbidden",
    "path": "/api/admin/user/54/wallet"
}

Viceversa if I try to call the previous API passing a token of an user having the AMDIN authority I obtain the expected API output.

It seems pretty strange to me because into the CLIENT_MATCHER I have defined this rule:

"/api/admin/user/{userID}/wallet"

The strangest thing is that if I remove this line from the ADMIN_MATCHER:

"/api/admin/user/{userID}/wallet",

that become something like:

private static final String[] ADMIN_MATCHER = { 
                                                    "/api/users/email/**",
                                                    "/api/admin/users",
                                                    //"/api/admin/user/{userID}/wallet",
                                                    "/api/admin/**"
                                                    }

Now the behavior is the complete opposite: using a JWT token of an user having the CLIENT authority I retrieve the expected output, but now using a JWT token of an user having the ADMIN authoruty I obtain this error message:

{ "timestamp": "2022-02-13T20:27:47.793 00:00", "status": 403, "error": "Forbidden", "message": "Forbidden", "path": "/api/admin/user/54/wallet" }

and this seems pretty strange to me because the ADMIN_MATCHER contaisn this rule:

"/api/admin/**"

that should meaning: access to all the API having an enptoint starting with "/api/admin/" folloed by anything else.

What is wrong in my Spring configuration? What am I missing? How can I fix it in such a way that this API could be accessible using a JWT token having ADMIN or CLIENT authority?

CodePudding user response:

instead of

.antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN")

try

.antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN", "CLIENT")

and removing this from CLIENT_MATCHER

"/api/admin/user/{userID}/wallet"

CodePudding user response:

You wrote: .anyRequest().denyAll(). denyAll() is applied to deny all requests, even if they are from trusted source with authenticated users. This is the method, that is required to deny requests.

Spring docs for denyAll() state,

Specify that URLs are not allowed by anyone.

  • Related