[Pre Lab] Session 9.1: Spring Boot Security - in-memory authentication and authorization
1. Clone the following repository
$ git clone https://github.com/M-Gharib/ESI-W9.1.git
2. Add the security dependency
- Open command pallet in VSCode (Windows: Ctrl + Shift + P, Mac: Cmd + Shift + P).
- Choose "Spring Initializer: Add starters"
- Select "Spring security"
Or you can simply uncomment the following dependency in your Pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
3. Run your application and try to visit any of the endpoints of the application (e.g., "http://localhost:8090/api/public"), you will notice that your application will redirect you to a login page, where you are expected to enter your credentials! Anyway, there is nothing to be worried about; Spring Boot has automatically created a user and a password for you. You need just to check your application log (Terminal).
4. Use "user" as the username and the generated password as the password, and you will be able to access http://localhost:8090/api/public.
Why does this happen? When Spring Boot detects the spring-boot-starter-security
, it will secure the access to the application by defining a single user with a randomly generated password.
Note that you can override this default user by setting up the username and password for the default user within the application.properties
file, as follows:
#Defined default user credentials #spring.security.user.name = username #spring.security.user.password= password
5. Uncomment the username and password in application.properties
and run your application again. You should notice that Spring did not create a default password this time, and using the username and password you defined in application.properties
should be enough to have access to http://localhost:8090/api/public. However, a single user is not enough for securing and controlling access to your application. Accordingly, comment out the username and password in application.properties
because we do not need them anymore.
Creating in-memory authentication and authorization mechanism
Uncomment the code in config/SecurityConfig.java
, the userDetailsService
is used to create two users in-memory, assigning their user names, passwords, and roles. Moreover, we are using BCrypt Password Encoder which is a strong hashing function to encode and decode the passwords.
..... @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean //In memory user authentication and authorization public UserDetailsService userDetailsService(PasswordEncoder encoder) { UserDetails admin = User .withUsername("admin") .password(encoder.encode("admin")) .roles("ADMIN") .build(); UserDetails user = User .withUsername("user") .password(encoder.encode("user")) .roles("USER") .build(); return new InMemoryUserDetailsManager(admin, user); } .....
Configuring authorization/access rules
After registering our different types of users, we can define our access rules. We will control the access to our application (routes to endpoints) considering the roles that our users play in the system. This can be done with the securityFilterChain(..) function.
..... @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors(withDefaults()) //.csrf().disable() .authorizeHttpRequests(authorize -> authorize .requestMatchers("/api/public").permitAll() .requestMatchers("/api/admin").hasRole("ADMIN") .requestMatchers("/api/user").hasRole("USER") .anyRequest().authenticated() ) .formLogin(withDefaults()) .httpBasic(withDefaults()); return http.build(); } .....
The securityFilterChain(..) function takes the HttpSecurity as a parameter, and defines the following configurations:
.cors(withDefaults()
supports the Cross-Origin Request Sharing (CORS) mechanism to avoid CORS errors since our REST API is to be accessed from a Vue.js frontend application that will run on a different port. We have already done the same for our rest controllers if you remember. However, we also need to adapt the configuration of Spring security as well..csrf().disable()
we do not need it here, but we will need it later. We are disabling the protection against cross-site request forgery attacks (CSRF) since we are only using a non-browser client..authorizeHttpRequests
restricts access based on RequestMatcher implementations..requestMatchers("/api/public").permitAll()
ensures that any request to ("/api/public") is permitted..requestMatchers("/api/admin").hasAnyRole("ADMIN")
ensures that any request to ("/api/admin ") should be authenticated and only users with Role ("ADMIN") are allowed..requestMatchers("/api/user").hasAnyRole("USER")
ensures that any request to ("/api/user ") should be authenticated and only users with Role ("USER") are allowed.
Note that you can use hasRole instead of hasAnyRole if your system allows only one role for the user. Also, you may find hasAuthority is some Spring Boot applications, theoretically, it is equivalent to hasRole. Also, note that the use of .hasAnyRole("ADMIN") is not authentication but authorization as both types of users can be authenticated by only users with the role "ADMIN" who can access this route.
.authenticated()
requires that all endpoints called be authenticated before proceeding in the filter chain..formLogin(withDefaults())
allows users to authenticate with form-based login (username/password in a form)..httpBasic(withDefaults())
allows users to authenticate with HTTP Basic authentication, i.e. sending in an HTTP Basic Auth Header to authenticate.
Test our application
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 any 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.