Arvutiteaduse instituut
  1. Kursused
  2. 2024/25 kevad
  3. Ettevõttesüsteemide integreerimine (MTAT.03.229)
EN
Logi sisse

Ettevõttesüsteemide integreerimine 2024/25 kevad

  • Home
  • Lectures
  • Practicals
  • Assignements
  • Project and exam
  • Message Board

Session 9.2: Spring Boot Security - Authentication and Authorization through JPA/DB

1. Clone the following repository

$ git clone https://github.com/M-Gharib/ESI-W9.2.git

If you want to create a new Spring Boot project from scratch, you need to install the following dependencies for both the Product and Inventory services:

  • Spring Web
  • Spring Security
  • Spring Data JPA SQL
  • PostgresSQL Driver SQL;
  • Lombok

The application has the following structure.

week10
└── config
      └──  MyUserDetails.java
      └──  MyUserDetailsService.java
      └──  SecurityConfig.java
└── controller
      └──  userController.java
└── model
      └──  User.java
└── repository
      └──  userRepository.java
└── service
      └──  userService.java

2. Check the code in User.java, which is used to create a table in the Postgres database to store the users and their credentials. Note how we are using a Universally Unique IDentifier (UUID) for the Id field, and how we are generating its value automatically.

3. Check the code in userController.java, userService.java, and userRepository.java. There is only a new request handler for adding a new user in userController.java, and a corresponding function addUser(User user) in userService.java. Note how we are encoding the password before adding the user to the database in the addUser(User user) function.

4. Check the code in SecurityConfig.java,

  • passwordEncoder is the same one we used in the in memory auth example.
  • UserDetailsService, unlike in memory auth, UserDetailsService returns an instance of our userDetailsService (MyUserDetailsService()).
  • securityFilterChain(HttpSecurity http) is almost the same as the one we used in the in-memory example, except we are using .hasAuthority instead of .hasAnyRole since we are using Authority in our UserDetails (MyUserDetails).
  • authenticationProvider() we are using DaoAuthenticationProvider, which is an AuthenticationProvider implementation that uses a UserDetailsService and PasswordEncoder to authenticate a username and password.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

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

    @Bean
    public UserDetailsService userDetailsService() {
    return new MyUserDetailsService();
    }

    // Note that, unlike in memory authentication, we have changed ".hasAnyRole/.hasRole" to ".hasAuthority"
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
      return  http
                .cors(withDefaults())
                //.csrf().disable()
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(authorize -> authorize
                                .requestMatchers("/api/public", "/api/new").permitAll()
                                .requestMatchers("/api/admin").hasAuthority("ADMIN")
                                .requestMatchers("/api/user").hasAuthority("USER")
                                .anyRequest().authenticated())
                .formLogin(withDefaults())
                .httpBasic(withDefaults())
                .build();
    } 

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

5. Check the code in MyUserDetails.java, the MyUserDetails class extends the User class and implements UserDetails interface as required by Spring Security. Via its getAuthorities() function, it fetches the user roles and assigns them to authority.

public class MyUserDetails extends User implements UserDetails  {

    private User user;

    public MyUserDetails(final User user) {
        this.user = user;
    }

    @Override
    public List<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRoles());
        return Arrays.asList(authority);
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getName();
    }

   //hard-coding these attributes
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    ...

6. Check the code in MyUserDetailsService.java, the MyUserDetailsService implements the UserDetailsService interface as required by Spring Security.

Via its loadUserByUsername() function, it checks whether the user exists in the database, and if it does, it will map its attributes to the MyUserDetails. If the user does not exist, it will throw an exception.

@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
       Optional<User> user = userRepository.findByName(username);
       user
       .orElseThrow(() -> new UsernameNotFoundException(username + "not found"));
               return user.map(MyUserDetails::new).get();
   } 
}

Test our application

7. Run your application.

8. create an admin and user users, in the RestClientFile.rest, there are two HTTP requests, which can be used to create both of these users.

### Add a user with Role/Authority ADMIN
POST  http://localhost:8090/api/new HTTP/1.1
content-type: application/json
{
    "name": "admin",
    "password": "admin",
    "roles": "ADMIN"
}

### Add a user with Role/Authority USER
POST  http://localhost:8090/api/new HTTP/1.1
content-type: application/json
{
    "name": "user",
    "password": "user",
    "roles": "USER"
}

9. In RestClientFile.rest, there are three HTTP requests, the first is a public endpoint, i.e., not protected, and the other two requesters are protected and can be accessed by users who have roles of "ADMIN" and "USER" respectively. If you run the following requests in RestClient you will receive a form (an HTML page) as we configure our security (.formLogin(withDefaults())) to use a form for checking the credentials of a user. Therefore, use the browser when you want to try each of them.

### Public endpoint
GET http://localhost:8090/api/public

### Protected endpoint - only admins are allowed - Username: admin Password: admin
GET http://localhost:8090/api/admin

### Protected endpoint - only users are allowed - Username: user Password: user
GET http://localhost:8090/api/user

Note When you try to log in a cookie will be created and saved in your browser, which may prevent you from trying to log in again using other credentials. In order not to wait until the cookie expires or rerun your application, you can delete this cookie manually, as follows: right-click (anywhere on the webpage), select Inspect, and navigate to the application tab. The cookie will have the name http://localhost:8090, right-click on it, then, clear. After clearing the cookie, you can try to log in again using any credentials you want.


Clearing a cookie
  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused