How to use RESTful with Basic Authentication in Spring Boot

Viewed 6697

Helllo, I'm using RESTful with basic authentication and this code is a part from the RestController:

@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username) {
    userId = getUserIdFromUsername(username);
    return goalJpaRepository.findByUserId(userId); 
}

public Long getUserIdFromUsername(String username) {
    User user = userJpaRepository.findByUsername(username);
    userId = user.getId(); 
    return userId;
}

And I have a problem, for example I'm using Postman to retrieve the goals for a speciffic user like this:

http://localhost:8080/jpa/users/john/goals with GET request

Then I use the basic authentication for the username john and the password for this username and I receive the goals for john.

After that if I do a GET request for this link http://localhost:8080/jpa/users/tom/goals I receive the goals for tom, but I'm logged in with john at this moment of time, so john can see his goals and also he can see tom's goals.

The question is how can I access the login username in the RestController, because I want to do something like this:

if (loginUsername == username) {
    return goalJpaRepository.findByUserId(userId);
} 

return "Access denied!";

So I want to know if it is possible to access the login username from HTTP Header?

Thank you!


UPDATE - Yes the framework is Spring Boot, also I'm using Spring Security with Dao Authentication because I want to get the user from a MySQL database. Anyway I'm not an expert at Spring Security.

Now I understand how to use Principal in my controller methods, but I don't know how to use Spring Security for this specific case. How should I implement it? For example the user john should see and modify only his goals.

Spring Security Configuration:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.dgs.restful.webservices.goaltrackerservice.user.MyUserDetailsService;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {

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

    @Autowired
    private MyUserDetailsService userDetailsService;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            .antMatchers("/allusers").permitAll()
                .anyRequest().authenticated()
                .and()
            // .formLogin().and()
            .httpBasic();
        }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }
}
4 Answers

Assuming you are using Spring as your Java framework, you should use Spring security to configure the basic authentication. Many tutorials available online (https://www.baeldung.com/spring-security-basic-authentication,

Spring Security will then provide a security context available throughout the app (SecurityContextHolder.getContext()) from which you can retrieve the connected user information (username, ...).

For instance to retrieve the username of the connected user, you should do :

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String loginUsername = authentication.getName();

Alternatively, as mentioned by @gervais.b Spring can inject the Principal (or Authentication) in your controller methods.

As said by @Glains An even better alternative is to use the @PreAuthorize and @PostAuthorize annotations, which alows you to define simple rules based on Spring Expression Language.

Please note that you are not doing any security at this time.

As said by @Matt "It depends which framework you are using". But I guess you are using spring. You should then have a look at the spring-securuty module documentation.

Basically you can inject the authenticated user into your method parameter :

   @GetMapping("/jpa/users/{username}/goals")
   public List<Goal> getAllGoals(@PathVariable String username, Principal principal) {
     if ( username.equals(principal.getName()) ) {
       userId = getUserIdFromUsername(username);
       return goalJpaRepository.findByUserId(userId); 
     } else {
       throw new SomeExceptionThatWillBeMapped();
     }
   } 

But spring-security and many frameworks provide better patterns to manage the security.

You can also solve this problem with @PreAuthorize, an annotation offered by the Spring Security Framework, which uses the Spring Expression Language.

@PreAuthorize("principal.name == #username")
@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username) {
    return goalJpaRepository.findByUserId(userId); 
}

Behind the scenes Spring will use the already mentioned SecurityContextHolder to fetch the currently authenticated principal. If the expression resolves to false, the response code 403 will be returned.

Please note that you have to enable global method security:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

}

To answer your new question on "Dao Authentication" the answer is to provide a custom UserDetailsService.

From the configuration that you have attached to your question it looks that you already have a MyUserDetailsService.

There are plenty of articles that explain how to use a custom DetailsService. This one seems to match your requirements : https://www.baeldung.com/spring-security-authentication-with-a-database

Edit: On how to be sure that only John can see John's items.

Basically, the only ting that you can do to esnure that only John can see his goals it to restrict the goals to only those owned by John. But there is plenty way of doing this.

  1. As you suggest in your initial question, you can just select the goals for a specific user. The power with spring-security is that it can inject a Principal but also kind of orher authentication object.

  2. You can also make that more implicit an filter a the DAO/Repository side by using the SecurityContextHolder. This approach is fine and looks better when your system is more user centric or like a multi-tenant system.

  3. Using some specific @Annotations or Aspects would also be a solution but maybe less obvious in this case.

Related