Wednesday, April 10, 2019

Kafka and Spring

Kafka has become a very popular platform and is being used as a stream, journal and even eventing system. In this post, we explore how to integrate Kafka with spring framework application. First, we add the Kafka bootstrap server details in the application.properties file.

Let's also add dependencies in pom.xml.

Now, for each Kafka topic, we create a listener class. The listener class provides a callback method that is called when any message is retrieved on that topic.

Now we create an endpoint through which we inject a message in the queue. The message is sent to the queue and is retrieved by the listener.

We autowire a KafkaTemplate instance that is used to send the message to the queue.

$ curl -X POST \
>   'http://localhost:8081/send?token=3193fa24-a0ba-451b-83ff-eb563c3fd43b-cdf12811-7e41-474b-8fa6-e8fefd4a738c' \
>   -H 'Content-Type: application/json' \
>   -H 'Postman-Token: e281e3c5-0dae-4bb7-ac8d-6555f66a18c6' \
>   -H 'cache-control: no-cache' \
>   -H 'token: 3193fa24-a0ba-451b-83ff-eb563c3fd43b-cdf12811-7e41-474b-8fa6-e8fefd4a738c' \
>   -d '{
> "message" : "This is my message!"
> }'
Message sent successfully!.
The receipt of message is indicated in the spring server log.

2019-04-10 14:28:03.969  INFO 31091 --- [ntainer#0-0-C-1] i.s.b.t.listeners.MyTopicKafkaListener   : Received Promise message This is my message!

Sunday, March 10, 2019

Spring Boot and Docker Containers

With microservices based deployment, the first step is to dockerize your software. In our case, we want to create docker images for each of our microservices so that we can orchestrate them better. I have decided to use container registry provided by Google to build and upload the images.

The first thing we need to do is to create a dependency to spring-cloud-dependencies pom. Then we add a dependency to spring-cloud-config-server. Then we add a dockerfile-maven-plugin. Now we need to keep in mind that in the configuration for dockerfile-maven-plugin we need to provide the repository. If can see that our repository starts with gcr.io/. This makes sure that the image after creation is pushed to the container registry hosted by Google. If you want to have some other registry, you need to provide it in the form hostname:portnumber.
Now we can issue the following command the docker image would be build and pushed to google registry.

$ mvn deploy -DskipTests
[INFO] 
[INFO] --- dockerfile-maven-plugin:1.4.10:push (default) @ customer ---
[INFO] Using Google application default credentials
[INFO] loaded credentials for user account with clientId=764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com
[INFO] The push refers to repository [gcr.io/myproject/rae/customer]
[INFO] Image 967d96afcc46: Preparing
[INFO] Image 36e051842720: Preparing
[INFO] Image d1646aaa6540: Preparing
[INFO] Image 19382582b926: Preparing
[INFO] Image 41715d8d7d2b: Preparing
[INFO] Image f3a38968d075: Preparing
[INFO] Image a327787b3c73: Preparing
[INFO] Image 5bb0785f2eee: Preparing
[INFO] Image f3a38968d075: Waiting
[INFO] Image a327787b3c73: Waiting
[INFO] Image 5bb0785f2eee: Waiting
[INFO] Image 36e051842720: Layer already exists
[INFO] Image 41715d8d7d2b: Layer already exists
[INFO] Image d1646aaa6540: Layer already exists
[INFO] Image 19382582b926: Layer already exists
[INFO] Image 967d96afcc46: Pushing
[INFO] Image a327787b3c73: Layer already exists
[INFO] Image 5bb0785f2eee: Layer already exists
[INFO] Image f3a38968d075: Layer already exists
[INFO] Image 967d96afcc46: Pushed
[INFO] 0.0.1-SNAPSHOT: digest: sha256:f6bad4811f867dd75225797bee684ea43c0ddaf2b83de1b419a9f75e9a3941bc size: 2001
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 47.638 s
[INFO] Finished at: 2019-03-10T19:58:27+05:30
[INFO] Final Memory: 55M/857M
[INFO] ------------------------------------------------------------------------

We can see below that the image is now pushed to google container registry.
$ docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
gcr.io/myproject/rae/customer   0.0.1-SNAPSHOT      a566e2f28705        19 seconds ago      518MB
gcr.io/myproject/rae/customer                       8c61d1a5aef4        13 minutes ago      518MB

Tuesday, February 12, 2019

12. Adding GIT release information

In the previous post, we saw how to enable actuator endpoints on our spring server. Once we have done that, it is a good idea to add GIT release information to the server in order to get the information related to the currently deployed server at runtime.
We add the git-commit-id-plugin to our pom.xml.

   <plugin>
    <groupid>pl.project13.maven</groupid>
    <artifactid>git-commit-id-plugin</artifactid>
    <version>2.2.1</version>
   </plugin>
The next things to do is to create a git.properties file in the resource directory of the project.

The git-commit-id is added by the maven plugin so it is a good idea to build the project using maven.

$ mvn clean package -DskipTests
Now we can run the server and check the /manage/info endpoint using curl command.
$ curl -X GET http://localhost:9091/manage/info
{"git":{"branch":"master","commit":{"id":"6fb94c0","time":1549868235.000000000}}

11. Spring Actuators

Spring provides actuators that are a helpful set of tools to debug the application on runtime. Here is how to enable them. We first add actuator dependency in the pom.xml
<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-actuator</artifactid>
</dependency>

Now we define a prefix for all the actuator endpoints. We add the following line into the application.properties file. This enables all the actuator endpoints. We can enable specific endpoints by adding a comma delimited list of endpoints. We also deploy management endpoint on a separate port so that we can block its access from something like ELB.
management.endpoints.web.base-path=/manage
management.server.port=9091
management.endpoints.web.exposure.include=*
Since we already have a security filter defined, we need to exempt health and info endpoint from security check. We add the following URLs int he SecurityConfiguration configure method.
@Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/manage/health");
        web.ignoring().antMatchers("/manage/info");
        web.ignoring().antMatchers("/webjars/**");
        web.ignoring().antMatchers("/error");
        web.ignoring().antMatchers("/swagger-ui.html");
        web.ignoring().antMatchers("/v2/api-docs/**");
        web.ignoring().antMatchers("/swagger-resources/**");
    }
Here we have added paths related to error, actuator, and swagger.
This enables actuator endpoints for our server. We can query these endpoints and following is the sample response.

$ curl -X GET http://localhost:9091/manage/health
{"status":"UP"}

Monday, February 11, 2019

10. Application with multiple datasources

Many times it is a practical requirement to have multiple databases for a single application. These databases could be at different locations on the cloud and different entities in your application may be dealing with these databases.
Multi-tenancy is a great requirement when multiple data sources are needed. Many tenants may insist on having their own databases. Here we present how we can configure the spring application to interact with multiple databases.
The first step is to look at our application.properties file. We have a list of properties defined for the default dataSource which we will need to replicate for our second database. Let's assume we are going to use two data sources, the first one is called the user and the second one is called other.

As we can see above, we have replicated all the data source properties and given it a new prefix, other. Now, these two databases could have completely independent settings. They could point to totally different databases. Each of these properties could be configured to completely different settings. Here we have just changed the name of the database, username, password.
To make this work. we will have to split the repositories and the entity objects for each of the data sources. Here we create the following hierarchy of packages for each of the data sources.
src/main/java
- in.springframework.blog.tutorials
 - user
    - domain
    - repository
  - other
    - domain
    - repository
As we can see for each of the data sources, we have a domain package that would contain the entities and a repository package that would contain the repository class. This is needed so that each of the entity managers only searches for its own classes.
Now we need to define a configuration of each of the data sources that we have defined. The first data source will contain the user table and will also be the primary data source.

As we can see in the class above, it uses all the properties prefixed with spring.datasource and scans directories related to the user data source. Now let's look at the configuration for other data source.

The things to note in the other data source is that the @Primary annotation doesn't exist because we can have only one set of primary beans of a type. Also, the directories to be searched are for the other data source's domain and repository objects.
At this time we also move the old User and UserRepository classes to their respective subdirectories. We also create Other and OtherRepository classes in their respective subdirectories. We also change CrudRepository to JpaRepository in each of the repository classes.
Now, our application is set up to use two different data sources and we can verify that by running the application. Since we have set ddl-auto property to update, it should create a new schedule when the application is run.
$ mysql -u tutorial -ptutorial123 tutorial
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 115
Server version: 8.0.12 Homebrew

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show tables;
+--------------------+
| Tables_in_tutorial |
+--------------------+
| hibernate_sequence |
| user               |
+--------------------+
2 rows in set (0.00 sec)

mysql> desc user;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| id         | bigint(20)   | NO   | PRI | NULL    |       |
| email      | varchar(255) | YES  | UNI | NULL    |       |
| fullname   | varchar(255) | YES  |     | NULL    |       |
| password   | varchar(255) | YES  |     | NULL    |       |
| username   | varchar(255) | YES  | UNI | NULL    |       |
| auth_token | varchar(255) | YES  | UNI | NULL    |       |
| expiry     | datetime     | YES  |     | NULL    |       |
| mask       | bigint(20)   | NO   |     | NULL    |       |
| authToken  | varchar(255) | YES  |     | NULL    |       |
+------------+--------------+------+-----+---------+-------+
9 rows in set (0.00 sec)

$ mysql -u other -pother123 other
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 116
Server version: 8.0.12 Homebrew

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show tables;
+--------------------+
| Tables_in_other    |
+--------------------+
| hibernate_sequence |
| other              |
+--------------------+
2 rows in set (0.00 sec)

mysql> desc other
    -> ;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id        | bigint(20)   | NO   | PRI | NULL    |       |
| otherData | varchar(255) | YES  |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
The source code for this tutorial is available at git repository as v1.2.

Wednesday, January 23, 2019

9. Role based data access

Many times we have a need to return data from an endpoint based on the role. Springframework provider easy mechanism for us to be able to do that. Let's take an example. In the previous example, we want to add a GET method in UserEndpoint that returns all the users. For any safe system, we want to return all the users if the role is ADMIN but if the role is USER then we want to return only that particular user. We don't want to write multiple methods for that purpose.
    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @PostFilter("hasAuthority('ADMIN') or filterObject.authToken == authentication.name")
    public Iterable getUsers() {
        Iterable users = userRepository.findAll();
        return users;
    }
The method is described above. As we can see it is an extremely simple method, it calls findAll method on the repository which will return all the valid user records and returns an Iterable collection back. The interesting aspect of this method is in the @PostFilter annotation. Let's try to understand the annotation. The first condition is hasAuthority('ADMIN'). It implies that if the authenticated role is ADMIN then return the records as it is. The next bit of the filter condition uses an object called filterObject. This is an automatically defined expression that we can use in @PostAuthorize filter. A complete list of all the expressions exists here. The expression filterObject is used for each element of a collection that is returned from the endpoint. Since we know that when this endpoint is called, we would only be authenticating using authToken, the name of authentication in security context is set to the token itself. We can verify this in the code in AuthenticationFilter class.
            else {

                Optional token = getOptionalHeader(httpRequest,"token");
                TokenPrincipal authTokenPrincipal = new TokenPrincipal(token);
                processTokenAuthentication(authTokenPrincipal);
            }

The code fragment described above creates a TokenPrincipal object with the token as its name. That is the reason we have the condition filterObject.authToken == authentication.name. Taking the complete condition of @PostFilter we can see that the condition implies that return everything unconditionally if the role is ADMIN otherwise return the users with authToken as the currently authenticated user.
We have two users defined on the system. One has a role of the user and the other has a role of admin. Here are the examples of what happens when we call the endpoint with both of these users.
The first example is with a user with role USER.
$ curl -X GET "http://localhost:8081/user" -H "accept: application/json" -H "token: 3d47912d-73a0-4c4c-95e6-0486273d6221-28fa4f38-0f1b-4740-8e1d-3228288de631" | python -m json.tool
[
    {
        "authToken": "3d47912d-73a0-4c4c-95e6-0486273d6221-28fa4f38-0f1b-4740-8e1d-3228288de631",
        "email": "user@springframework.in",
        "expiry": 1548345909000,
        "fullname": "User",
        "id": 2,
        "mask": 1,
        "password": "User123",
        "username": "user"
    }
]

The second example is with a user with role ADMIN.
$ curl -X GET "http://localhost:8081/user" -H "accept: application/json" -H "token: a137dd09-11e4-4dcf-a141-0b235d39a505-60d43bf4-3674-4248-be1d-c2669f14589f" | python -m json.tool
[
    {
        "authToken": "3d47912d-73a0-4c4c-95e6-0486273d6221-28fa4f38-0f1b-4740-8e1d-3228288de631",
        "email": "user@springframework.in",
        "expiry": 1548345909000,
        "fullname": "User",
        "id": 2,
        "mask": 1,
        "password": "User123",
        "username": "user"
    },
    {
        "authToken": "a137dd09-11e4-4dcf-a141-0b235d39a505-60d43bf4-3674-4248-be1d-c2669f14589f",
        "email": "admin@springframework.in",                                                                                         
        "expiry": 1548348263000,                                                                                                     
        "fullname": "Administrator",                                                                                                 
        "id": 3,                                                                                                                     
        "mask": 4,                                                                                                                   
        "password": "Admin123",                                                                                                      
        "username": "admin"                                                                                                          
    }
]

As we can see above, the call with the role USER only returns the object related to that particular user while the call with the role returns all the users present in the system. This is how we can achieve role based object access without writing multiple endpoints.

Tuesday, January 22, 2019

8. That little matter of creating a user

Now that our simplified authentication system is in place, we are faced with the little matter of how to create a new user. Since we don't have a username, password, or token we can't really create a new user.
To accomplish that, we need to make some modifications to our AuthenticationFilter.  In the doFilter method, we add a special if block to take care of user creation.
            if (httpRequest.getRequestURI().toString().equals("/user") && httpRequest.getMethod().equals("POST")) {

                Optional username = getOptionalHeader(httpRequest,"username");
                UsernamePasswordPrincipal usernamePasswordPrincipal = new UsernamePasswordPrincipal(username, username, true);
                processUsernameAuthentication(usernamePasswordPrincipal);
            }
            else if (httpRequest.getRequestURI().toString().equals("/authenticate") && httpRequest.getMethod().equals("POST")) {

                Optional username = getOptionalHeader(httpRequest,"username");
                Optional password = getOptionalHeader(httpRequest,"password");
                UsernamePasswordPrincipal usernamePasswordPrincipal = new UsernamePasswordPrincipal(username, password);
                processUsernameAuthentication(usernamePasswordPrincipal);
            }

As we can see the code fragment, if the call is made to /user endpoint with POST method, we look for a username header and trigger spring authentication. We also need to make a change in the UsernamePasswordPrincipal to take a flag that would tell us if the user is a new user or existing user.
Now that we have modified the principal and filter, we need to handle this in the provider. The provider that gets invoked for username and password authentication is UsernamePasswordAuthenticationProvider.
As we can see in the provider's authenticate method, we have added an if block that checks if this is a new user. In case it is a new user, we authenticate this user with a role NEWUSER. The create user endpoint is only allowed to be called for a role NEWUSER.
Now that we have stitched the path for authentication of a new user to create user endpoint, we can see the endpoint itself.
    @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    @PreAuthorize(MyConstants.ANNOTATION_ROLE_NEWUSER)
    public Optional createUser(@RequestHeader(value="username") String username, @RequestBody User user) {
        user.setMask(Role.USER.ordinal());
        User storedUser = userRepository.save(user);
        storedUser.setPassword(null);
        return Optional.of(storedUser);
    }
As we can see, we have a @PreAuthorize added with NEWUSER role. We also set the role of the user to the USER. Before returning the response, we set the password to null.
This fixes our service to allow the creation of a new user. The complete code is available tagged as v1.1 for this and previous posts.

7. Enabling Swagger

Before we proceed any further, let's enable swagger on our service so that testing it becomes easy. The first step is to add springfox dependencies.

Now that we have dependencies added, we need to define a swagger configuration. Most of our endpoint will need to have a token passed as HTTP header, we need to configure our swagger configuration to facilitate that.

We create a swagger configuration file as defined above. The globalOperationParameters clause dictates to swagger that each endpoint will have a parameter named token of type header and is mandatory. We know there is two endpoint that will not have a token header but we can just pass some dummy values. We can also make it non-mandatory if we so wish.

We also modify authentication endpoint to take a username and password header for authentication endpoint. That is defined in the AuthenticateEndpoint as above. Look at the @RequestHeader parameter passed in the login method of the endpoint.
        web.ignoring().antMatchers("/swagger-ui.html");
        web.ignoring().antMatchers("/v2/api-docs/**");
        web.ignoring().antMatchers("/swagger-resources/**");


Also, make sure that SecurityConfiguration.java that we had defined in one of the previous posts a list of swagger related URLs were made part of an exception list so that these do not go through authentication filter.
Now if we rebuild the server and run it and visit http://localhost:8081/swagger-ui.html we see the swagger page with all the API endpoints listed there. Here is the screenshot of the swagger UI as seen on visiting above URL.

Friday, January 4, 2019

6. Introducing Spring Security

If the web services that we are building require any level of authentication and authorization, it is better to understand Spring security context. Services that are properly built with security context can implement a better level of security at each method and endpoint level.
We want to handle all the security, authentication and, authorization related code in a single block so that managing it becomes easier.
The first step that we need to do is to add security dependencies in the pom.xml.
For authentication and authorization to make sense, we need to have a concept of a Role within the system. The roles are required to authorize the users for specific purposes. We define a set of roles in the form of a bitmask defined as an enum.

Now that we have defined Roles, we need to add few fields in our User class. We add a field mask for holding allowed roles and two different fields for authToken and expiry. We also add an index in the User entity.

We also need to add a class that implements GrantedAuthority interface. An authority in the spring security system is represented by a string and we use the Role enum name as the authority in the system.

We probably need to have two different types of authentication and authorization within the server. The first authentication and authorization will work with username and password and the second one will work with the token. Spring security requires two different entities to be defined for authentication and authorization. We need a Principal and a Provider class each for username and token authentication and authorization. Let's first look at the class required for authentication using username and password.  A principal is nothing but the abstraction of credentials that is flowing through spring security. We define it as below.

A provider is a class that provides the authentication and authorization functionality for a particular type. The username and password provider is defined as below. As we can see, the provider class extracts the credentials from the Principal and then makes sure the password is correct for the username. It also populates the list of roles granted to the user in the form of Authority.

Similarly, we define a Token principal. This principal just contains the token value that is received from the user.

We also define a token provider. The token provider looks up the user from the database by querying it from token and authenticates the user. In this example, we are making an implicit assumption that a token will be unique across all the users. If that were not to be the case, we will also have to pass the username or some other identifier for the user along with the token during authentication.

We also define a class RequestContext that will contain a set of thread local variables so that once authenticated, we will not need to look up the user details from the database. We will only need to hit the database in cases where we need to update the record in the User table.
We can see in both provider classes, after successful authentication of the user, the User is set into the thread local so that we can use it within the thread boundaries of that request.
Now the providers and principals are in place, we need to start using them. Rather than using them on a case to case basis, the best way is to define a filter which applies on each request and authentication and authorization can be done on all requests.

AuthenticationFilter provides the doFilter method which is called for each incoming request. As we can see within the doFilter method, if we receive a POST request to /authenticate URL we assume that to be a username and password authentication and we process it as a username and password authentication, in all other cases, it is assumed to be a token-based authentication. All the three parameters, username, password, and token are expected to be passed as headers.
Now we need to plug everything in so that it is called by the system. For that, we need to define a security configuration.
The SecurityConfiguration class is defined by extending WebSecurityConfigurerAdapter class. We override a few sets of methods. The first method defines a set of authentication providers that are available to the system.
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(tokenAuthenticationProvider());
        auth.authenticationProvider(usernamePasswordAuthenticationProvider());
    }

The second method defines a set of URLs that would be ignored by the security system.
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/manage/health");
        web.ignoring().antMatchers("/swagger-ui.html");
        web.ignoring().antMatchers("/webjars/**");
        web.ignoring().antMatchers("/v2/api-docs/**");
        web.ignoring().antMatchers("/error");
        web.ignoring().antMatchers("/swagger-resources/**");
    }

The third method defines the security rules for all the requests.
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().
                sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
                and().
                authorizeRequests().
                antMatchers(actuatorEndpoints()).hasAuthority(Role.ADMIN.name()).
                anyRequest().authenticated().
                and().
                anonymous().disable().
                exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint());;

        http.addFilterBefore(new AuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
    }

The configuration first disables CSRF and then sets a session management policy. After that, it declares that all the actuator endpoints need to be authenticated by a user having the ADMIN roles. Then all other users need to be authenticated. With this, every request coming into the system will be automatically authenticated and authorized. Now we can annotate our endpoints with specific roles so that unauthorized users can not make the calls. For example, we add the following annotation to our endpoint that we had defined earlier.
@PreAuthorize("hasAuthority('USER')")

This annotation implies that a user with the role of USER can only call this endpoint. Any other user would not be able to call this endpoint.

This is also our AuthenticationEndpoint. This will need to be called when a user wants to login using username and password. This now provides us with a functioning service with spring security enabled. In future posts, we will provide more details on it.  The complete code for this post is available in github repository with version 1.0. Click here to download it. 

Wednesday, December 19, 2018

5. Replacing tomcat with jetty

By default when we run spring boot application, it runs a tomcat server. I have personally found tomcat too heavyweight and I prefer to use Jetty for writing a webservices backend.
2018-12-19 10:54:08.403  INFO 16029 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

2018-12-19 10:54:08.424  INFO 16029 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]

2018-12-19 10:54:08.424  INFO 16029 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.12

To replace tomcat with Jetty, we need to make a couple of changes.  The first step is to add Jetty dependencies in the pom.xml.
  
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jetty</artifactId>
  </dependency>
  <dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-deploy</artifactId>
  </dependency>
  <dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-rewrite</artifactId>
  </dependency>
  <dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-util</artifactId>
  </dependency>

Since springframework boot bom automatically includes a dependency on tomcat in spring-boot-starter-web, we specifically need to exclude it. So we change
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

to
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
    <exclusion>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
   </exclusions>
  </dependency>


The next step is to add a customizer for jetty which will allow us to set some parameters related to Jetty.
As we can see the customizer uses a set of variables that we can override in application.properties file to modify the behavior of the server.

2018-12-19 11:06:44.577  INFO 16219 --- [           main] o.e.jetty.server.AbstractConnector       : Started ServerConnector@42ebece0{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
2018-12-19 11:06:44.580  INFO 16219 --- [           main] o.s.b.web.embedded.jetty.JettyWebServer  : Jetty started on port(s) 8081 (http/1.1) with context path ''
Now the logs clearly show that in place of tomcat, it is running jetty.