Home > Back-end >  Can I mix both basic authentication and JWT token authentication to protect APIs of a single Spring
Can I mix both basic authentication and JWT token authentication to protect APIs of a single Spring

Time:11-26

I am pretty new in Spring Security and I am working on a Spring Boot project that uses Basic Authentication in order to protect some APIs. I am starting from an existing tutorial code (a Udemy course) trying to adapt it to my own use cases.

In this project I have this SecurityConfiguration used to configure the basic authentication.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
    private static String REALM = "REAME";
    
    private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
    private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
            http.csrf().disable()
                .authorizeRequests()
                .antMatchers(USER_MATCHER).hasAnyRole("USER")
                .antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
    
    @Bean
    public AuthEntryPoint getBasicAuthEntryPoint()
    {
        return new AuthEntryPoint();
    }

    /* To allow Pre-flight [OPTIONS] request from browser */
    @Override
    public void configure(WebSecurity web) 
    {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }

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

    @Bean
    @Override
    public UserDetailsService userDetailsService()
    {
        UserBuilder users = User.builder();

        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        manager.createUser(users
                .username("ReadUser")
                .password(new BCryptPasswordEncoder().encode("BimBumBam_2018"))
                .roles("USER").build());

        manager.createUser(users
                .username("Admin")
                .password(new BCryptPasswordEncoder().encode("MagicaBula_2018"))
                .roles("USER", "ADMIN").build());

        return manager;
    }
}

So from what I have understand:

Here it id defined the list of API that can be accessed by a nornmal user and the list of API that can be accessed by and admin user:

private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };

Into the previous configure() method basically it is stating that the API URL matching with the USER_MATCHER are accessible by logged user having role USER while API having URL matching ADMIN_MATCHER are accessible by logged user having role ADMIN. Is this interpretation correct?

Finnally the UserDetailsService bean simply define two users: one belonging to the USER "group" and the other one belonging to both the USER and ADMIN "group".

So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**

Is it my reasoning correct?

And now my doubt: into a controller class of this project I defined this method:

@RestController
@RequestMapping("api/users")
@Log
public class UserController {
    
    @Autowired
    UserService userService;
    
    //@Autowired
    //private BCryptPasswordEncoder passwordEncoder;
    
    //@Autowired
    //private ResourceBundleMessageSource errMessage;
    
    @GetMapping(value = "/test", produces = "application/json")
    public ResponseEntity<String> getTest() throws NotFoundException  {
        
        log.info(String.format("****** getTest() START *******"));
        
        return new ResponseEntity<String>("TEST", HttpStatus.OK);
        
    }
    
    ..............................................................................................................
    ..............................................................................................................
    ..............................................................................................................
}

As you can see this method handling a GET request toward the localhost:8019/api/users/test endpoint.

This endpoint URL is not in any of the previous two list related the protected endpoint (it is not into the USER_MATCHER list neither into the ADMIN_MATCHER list. So I expected that simply this endpoint was not protected and accessible to everyone. But performing the previous request using PostMan, I obtain this error message:

HTTP Status 401 : Full authentication is required to access this resource

So basically it seems to me that also if this endpoint not belong to any protected endpoint list it is in some way protected anyway (it seems to me that at least the user must be authenticated (infact trying both the previous user I can obtain the expected output, so it should mean that the endpoint is not protected by the user rule but it is protected againts not authenticated access).

Why? Maybe it depende by the previous configure() method settings, in particular this line?

.anyRequest().authenticated()

In case is it possible to disable in some way to implement something like this:

  • If a called endpoint belong to one of the previous two lists (USER_MATCHER and ADMIN_MATCHER) --> the user must be authenticated and need to have the correct role.

  • If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.

This approach make sense or am I loosing something?

I take this occasion to ask you also another information: do you think that it is possible to configure Spring security of this specific project in order to protect some specific endpoints using the basic authentication and some other specific endpoints using the JWT authentication.

Sone further notes to explain why this last question. This project is a microservice that at the moment is used by another microservice (used to generate JWT token) in order to obtain user information. (the other microservice call an API of this project in order to receive user information so it can generate a JWT token that will be used in my application. The comunication between these 2 microservice must use basic authentication).

Since this project contains all the entity classes used to map the tables related to the users on my DB, my idea was to use this project also for generic user management, so it could include functionality like: add a brand new user, changes information of an existing user, obtain the list of all the users, search a specific user, and so on.

These new APIs will be protected by JWT token because each API can be called from a specific user type having different privileges on the system.

So I am asking if in a situation like this I can add without problem 2 different types of authentication (basic authentication for the API that retrieve a user so the other microservice can obtain this info) and JWT authentication for all the other APIs. It make sense or is it better to create a brand new project for a new user management microservice?

CodePudding user response:

So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**

Yes.

Why? Maybe it depende by the previous configure() method settings, in particular this line?

Yes, when using .anyRequest().authenticated(), any requests that have not been matched will have to be authenticated.

If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.

You can achieve this by doing anyRequest().permitAll(). But this is not so secure because you are allowing access to every other endpoints, instead you should stay with anyRequest().authenticated() and allow access to specific endpoints manually, like so:

http
                .authorizeRequests()
                .antMatchers(USER_MATCHER).hasAnyRole("USER")
                .antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
                .antMatchers("/api/users/test").permitAll()
                .anyRequest().authenticated()
                ...
  • Related