Post

Udemy Spring boot course: Section 5 REST Api Security

Github Repo

Section goals

  • Secure Spring boot REST APIs
  • Define users and roles
  • protect urls
  • protect urls based on roles
  • store users, passwords and roles in DB (plain-text -> encrypted)
  • Just the basics, not too in depth

Spring Security Model

  • Implemented using Servlet filters in the background
  • Two methods of securing an app:
    • Declarative
    • Programmatic

Declarative

  • Define security constraints in configuration
  • @Configuration
  • Provides separation of concerns between application code and security

Programmatic

  • spring security provides an API for custom application codding
  • more customizable for specific app requirements

Servlet Filters

  • alt-text
  • Servlet filters are used to pre-process / post-process web requests
  • Servlets route web requests based on security logic
  • spring provides a bulk of security functionality with servlet filters

Spring Security Filters Workflow

  • alt-text

Enabling Spring Security

  • just add the dependency to your pom.xml
  • This will automatically secure all endpoints for the application
  • this will prompt for a username and password
    • default username: user
    • default password: generated in the console at startup
  • or you can set your own username and password by editting the application.properties file
1
2
spring.security.user.name=username
spring.security.user.password=mypassword123

Different types of authentication methods for spring security

Adding in memory users to Spring Security

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Configuration
public class DemoSecurityConfig {

    @Bean
    public InMemoryUserDetailsManager userDetailsManager(){
        // john
        UserDetails john = User.builder()
                .username("john")
                .password("{noop}test123")
                .roles("EMPLOYEE")
                .build();

        // mary
        UserDetails mary = User.builder()
                .username("mary")
                .password("{noop}test123")
                .roles("EMPLOYEE", "MANAGER")
                .build();

        // susan
        UserDetails susan = User.builder()
                .username("susan")
                .password("{noop}test123")
                .roles("EMPLOYEE", "MANAGER", "ADMIN")
                .build();

        return new InMemoryUserDetailsManager(john, mary, susan);
    }

}

Authorize with the VSCode Rest Client

  • in order to authenticate with the api you can add the following to the endpoints.rest file in vscode
    • Authorization: Basic john:test123
@host=localhost
@port=3000


###
# Get all employees
GET http://:/api/employees?sort=email,asc
Authorization: Basic john:test123

Restrict URLS based on Roles

Goal

HTTP MethodEndpointCRUD actionRole
GET/api/employeesRead allEmployee
GET/api/employees/{employeeID}Read singleEmployee
POST/api/employeesCreateManage
PUT/api/employeesUpdateManage
DELETE/api/employees/{employeeID}Delete EmployeeAdmin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    // configure the http requests that come in
    http.authorizeHttpRequests(configure ->
            configure
                    // employee roles
                    .requestMatchers(HttpMethod.GET, "/api/employees").hasRole("EMPLOYEE")
                    .requestMatchers(HttpMethod.GET, "/api/employees/**").hasRole("EMPLOYEE")

                    // manager roles
                    .requestMatchers(HttpMethod.POST, "/api/employees").hasRole("MANAGER")
                    .requestMatchers(HttpMethod.PUT, "/api/employees/**").hasRole("MANAGER")

                    // admin roles
                    .requestMatchers(HttpMethod.DELETE, "/api/employees/**").hasRole("ADMIN")
    );

    // use HTTP basic authentication
    http.httpBasic(Customizer.withDefaults());

    // disable csrc
    // in general, not required for stateless REST APIs.
    http.csrf(csrf -> csrf.disable());

    return http.build();
}

Request Matchers

  • alt-text

Security Filter Chain

DB authentication

  • username and password stored in db
  • we want the following users in the db just like when we had it with in memory user auth
User IDPasswordRoles
johntest123EMPLOYEE
marytest123EMPLOYEE, MANAGER
susantest123EMPLOYEE, MANAGER, ADMIN

Spring Security Database support

  • jdbc auth docs
  • Spring Security can read user account info from database
  • By default, you have to follow Spring Security’s predefined table schemas
  • alt-text
  • alt-text

Setting up users & authorities table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Create USERS table
CREATE TABLE USERS (
    USERNAME VARCHAR(128) PRIMARY KEY,
    PASSWORD VARCHAR(128) NOT NULL,
    ENABLED CHAR(1) CHECK (ENABLED IN ('Y','N')) NOT NULL
);

-- Create AUTHORITIES table
CREATE TABLE AUTHORITIES (
    USERNAME VARCHAR(128) NOT NULL,
    AUTHORITY VARCHAR(128) NOT NULL,
    CONSTRAINT AUTHORITIES_UNIQUE UNIQUE (USERNAME, AUTHORITY),
    CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) REFERENCES USERS (USERNAME)
);

Configuration to connect to sql tables

  • comment out or remove the in memory user storage solution
1
2
3
4
5
@Bean
public UserDetailsManager userDetailsManager(DataSource datasource){
    // tells Spring Security to use JDBC authentication with our data source
    return new JdbcUserDetailsManager(datasource);
}

Bcrypt implementation

  • No need to configure any java code, we just need to input the new bcrypt hash into the password fields of the users table records
  • you may need to adjust the size of the password field to 68 chars because that is what is required for bcrypt hashes
    • {bcrypt} - 8 char
    • encoded password - 60 chars
  • java just looks at what is inside the {} to determine what to do
    • ex {noop}test123 – says that this is a plain text password
    • ex {bcrypt} – says that it is a password that is hashed

Custom tables

  • if we have custom authentication tables we can do the following
  • in this case we have the two tables named members and roles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
public UserDetailsManager userDetailsManager(DataSource datasource){
    JdbcUserDetailsManager jdbcUserDetailsManager =  new JdbcUserDetailsManager(datasource);

    // define query to retrieve a user by username
    jdbcUserDetailsManager.setUsersByUsernameQuery(
            "select user_id, pw, active from members where user_id=?"
    );

    // define query to retrieve the authorities/roles by username
    jdbcUserDetailsManager.setAuthoritiesByUsernameQuery(
            "select user_id, role from roles where user_id=?"
    );

    return jdbcUserDetailsManager;
}

Implement Spring Boot REST Security with JPA Hibernate tutorial

This post is licensed under CC BY 4.0 by the author.