Learn about the Spring Security framework for user authentication and authorization.
- [Instructor] So far we have created our guest list and permissions by adding users and roles to the domain model. We need a front door to our application for the users. I created a /users API in this user controller class, and a login method where users will provide their credentials to sign into the application. We'll get back to the implementation of this method, but I want you to recall what happened when we added Spring secured dependency. It blocked all the applications, but the sign-in API should always be open, so we need to configure Spring security to unblock it, as well as the /tours and /packages API.
So let's see how we open those up. We have a new class in a new security package called webSecurityConfiguration.java. Web security configuration is a new class, and it will be the center point for setting up our security needs. It extends from Spring security class web security configuration adapter. It's annotated with @Configuration and @EnableGlobalMethodSecurity. We override web security configuration adapter's configure method in order to change the default behavior of the HTTP security object, so when we invoke the HTTP security object's authorize request methods, it will tell Spring security how to handle different APIs.
We want /tours and /packages to be open, so with antMatcher, we say anything that starts with the pattern /tours and /packages will be permitted. The double asterisk achieves this. Next, we want to unblock our new /users/signin API. There's no wild card because we need to exactly match the entire input. Anything else will require authentication. Because we created a stateless restful API, we need to tell Spring security that cross-site request forgery detection should be disabled, and that no sessions should be created for it.
So session creation policy is stateless. So let's try it out and see what we have so far. First, let's check to see if our /tours and /packages APIs are open. So let's get the tours. And that's working. And let's get the packages. And that's working. Next, let's see what happens when we invoke the user/signin method.
So this is a post to the user sign in end point, and our body is a JSON body, so the username is admin and the password is letmein. We'll get back to the object that describes the administrator, but let's send this and see what we get. It contains a list of authorities, which in Spring security speak are roles, and for the admin user there is only the role_admin role.
Authenticated is true. We also have a principal object. The principal is a generalized term that could be a user or another system that has credentials. Here, principal is the admin user, and again the authorities are listed. And there are settings about this user. Lastly is the user's name. Let's try it again with an invalid user. So I'm gonna just change the username to blah, which is not a user we have in our system.
And we see a 403 Forbidden is returned. Let's try to log in again with an invalid password. So change the username back to admin. And just instead of letmein, letmeout. So again, we get a forbidden. So we see that somehow this API must look up the security user table for entry with the username as admin and verifies the password is correct, and returns information about that user in the permissions.
Let's see how this is accomplished by running this application again in debug mode. So we'll stop the application, and this time, we'll run it again in debug mode. And I already have some break point set up. And at the start, we hit the break point in web security configuration, and that adds the configuration authentication manager as a bean in the applications context. You see that will execute down there.
Okay, so our application is up and running. Now let's try to sign in again. We'll put in the correct credentials. So we hit a break point in the login method and we're going to invoke the user service sign-in method, and that user service sign-in method will run the authentication manager, that authenticate method. Authentication manager is a bean that we've instantiated.
So let's invoke that, but we see that it's passing in the username and password into a username and password authentication token. Let's go in that, and now we have entered a new class. It's called ExploreCaliUserDetailsService. How did that happen? We didn't see the authentication manager actually call this. But the authentication manager requires an object that implements the user details service interface. It then invokes the object's load by username method, which we have overridden here.
It invokes the userRepository.findByUser method to see if there is an entry in the security user table with that username, and we throw an exception if it's not found. If it is found, we invoke a withUsername static method, let me make this a little bigger there. And this creates a user details object. It populates it with a username, password, and roles, and then adds some more settings to the object. The authentication manager verifies the password and interprets the user settings.
So we'll go back to, continue running there. And we'll see, we'll get the response of an OK. So there was a lot going on here, but the good news is, it's really not a lot of code on our part. Merely adding the authentication manger to the application context and implementing user details service was all we need for this step.
- Enhancing a Spring Boot microservice
- Hardening the microservice
- Configuring security with JSON web tokens
- Leveraging Docker for MySQL database access
- Dockerizing your microservice