Setting up the vue application
Although we could work on an existing application, I will propose to start with a brand new application (to reduce the risk of surprises). It will be a good exercise for you to transfer the components introduced here into your application.
Therefore, we will start by creating a new vue application with vue-cli as we did a few weeks ago. In a terminal window, you can run the following command (please select the router component in the configuration menu):
Note that we have already added buefy
for handling reactive user experience and
axios
for REST interactions. Now, open your project into a code editor (e.g. MS code).
If you are planning to use some icons in the app’s UI, add the reference to the
corresponding library in public/index.html
. For instance,
to use Google material icons, you should add the following line in the header
section of the aforementioned file:
To complete the setup, we have to import buefy and register it upon vue runtime.
The latter can be done by adding the following lines in file main.js
.
Adding a login page
Let us start by adding a vue component to handle the login of users
into our frontend application. I propose you to create a folder
auth
inside the folder components
. Then, create a file called
Login.vue
and copy there the following snippet.
Now, let us add a route to make the login page accessible to the
user. To this end, we would need to change the file router.js
as follows:
Note that the above will not change the application’s navigation component,
but paht /login
will now be recognized by the application.
Start your vue application and play with it a little bit. You can use the
command npm run serve
to that end.
Complete the authentication round trip
Right now, we have a visual component to let the users provide their credentials. Different from JWT, where the next step was to submit the credentials to the backend for authentication, we would need to encode the credentials on the frontend using Base64 and use the encoded value as the security token in every REST interaction.
What I would propose you is to keep the security token in local store and to remove it only when the user logs out. This simple scheme would allow us to simulate a web user session.
In addition to this, I propose you to use the login action as an opportunity to query the backend to know the roles played by the user and, with this information, implement a simple role-based access control. To implement this, let me first introduce the method that we will use in the backend. Create a controller class and copy there the following code:
Note that the method authenticate
would be reachable if and only if the request include the security token
in the headers. With such information spring security runtime can determine the username and also query
the database for the corresponding "authorities" (jargon for referring to the user’s roles). What the
method above is to retrieve the roles associated with the user and return them as a list.
We are now ready to add the javascript code for authentication. Copy the following snippet into file
components/auth/auth.js
.
(If you took the course on Agile software development, you will find that the code above is quite similar to the one used there). The code above defines a property "user" that keeps the user session’s information: the username, the set of roles associated with the user and a flag indicating if the user has been authenticated. The method login uses a REST interaction with the backend to retrieve the roles associated with the user that is trying to login. Of course, if the credentials are wrong, the information about the user will be kept as they were (usually referring to an nonexisting session). However, if the authentication succeeds, we will store the information about the session and redirect to the path that was sent as reference.
The statement btoa(username+":"+password)
is the one that computes the Base64 encoding for
the concatenation of the credentials. Note that we will copy that value into the headers
as part of the REST interaction (see {headers: {'Authorization': `Basic ${token}
}}`) and
later into local storage for future interactions, i.e.
window.localStorage.setItem('token-'+this.username, token)
.
The file also includes some other convenience functions:
-
Function
logout
cleans up the user session’s information (including local storage). -
Function
hasAnyOf
takes as input an array with roles as strings and checks if there is at least one matching with the roles of the currently logged in user. -
Function
authorized
returns true if the application has successfully checked the credentials of the user and his/her session is still valid (he/she has not logged out). -
Function
getAuthHeader
is meant to be used in parts of the code where we need to perform a REST interaction. The function would return a javascript object containing the configuration of the HTTP headers.
To try out, you would only need to comment out the lines in Login.vue
that would enable the call to auth.login
.
Restricting the access to content
With all the above, we have now the required infrastructure to implement the authentication process upon the backend. Since we have the information about the roles played by the logged in user, we will now implement a simple access control.
Firstly, we will secure the access to web pages using the router component from Vue. Please replace fully the content of file router.js
from your project with the following snippet.
The key section in the code above is the use of the "hook" function beforeEnter
. Such function is executed whenever we want the application to move to a certain page (according to the value of path). The function passes three parameters: to
that represents the target page, from
that represents the source page, and next
which is a callback function which can be used to perform a redirection.
As you can see, we have guarded the access to the home page, i.e. it will be accessible to a user if and only if the user has been authenticated. Note that we use the code from auth.js
to verify if the user has been authenticated. If the user is not authenticated, the application is redirected to the login page.
Note that we are using a very similar approach to implementing the functionality of logout
. The attempt to logout calls first auth.logout()
to clear all the user’s information (including the removal of the token from local storage). It then redirects the application to the login page.
Let us now update the application’s navigation bar, which is specified within file App.vue
. What I propose you is to change the reference to about
(generated by vue-cli) to refer now to logout. You can safely change the template included in App.vue
with the following snippet.
Finally, let me illustrate how we can use the function auth.hasAnyOf()
to control the access to certain parts of the user interface. To that end, I propose you to use vue’s directive v-if
with a call to auth.hasAnyOf()
, as illustrated in the following code:
Please note that we are specifying not only one role but an array of them. The function auth.hasAnyOf()
will check if there is at least one role matching between the list specified in the v-if
directive and the list of roles stored by auth
. If the matching is positive, one part of the UI will be render while the other one will remain hidden.
Of course, auth.hasAnyOf()
can be used in other places (e.g. in the router configuration to check, not only that the user has been authenticated, but if the user complies with the set of roles explicitly specified).
Authentication on integration flows and restTemplate
In this section, I will propose you an approach to add the security tokens to the REST interactions stemming from HTTP outbound gateways (cf. integration flows) and/or restTemplate. The approach assumes that the credentials are read from a properties files instead of hard coding them in the application.
Let us start by creating a property file called credentials.properties
within the folder resources
on your project. Copy there the following information and adapt it if required.
As you can see, I am assuming that we have to RentIt applications that are running in localhost in different ports. For each one of these RentIts we are going to store the string that we usually add to the headers of the HTTP request.
Now, let us figure out how to read the content of the file. Well, the solution is given in the following snippet. Copy the code into the corresponding java file.
As you can see the class is annotated with a reference to the property file and includes two has maps to store the values of authority and authorization specified in the property file. In fact, this is all we need. Let me show it how can we use it. To that end, replace the code of the entry class on your project with the following one:
If you run the new code, you will see that the main function will print out the content of the properties file.
Now, we will need to adapt a class that has remained hidden to us and that is responsible for creating an empty HTTP request, whenever we want to start a REST interaction. Copy the following code to your project.
The key part of the code above is the line ClientHttpRequest request = super.createRequest(uri, httpMethod);
, where we create the HTTP request. Note that we are passing the URI. The idea is then to compare the "authority" part of the URL with the one on the credentials property file, if we find a match, then we add the header Authorization
with the corresponding security token.
Let us now try out the HTTP request factory above. Change the code of the main function on the entry class with the following snippet:
Well, as you can see, we would need to get access to the bean ClientHttpRequestFactory
(usually with an @Autowired
) and we have to use it when we initialize the restTemplate. We would use something similar with an integration flow. Consider the following example: