Rest API Authentication | Spring OAuth 2.0 Resource Server, JWT, MongoDB, Spring Boot

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everyone in the last video we developed a spring back-end and used access and refresh tokens for authentication we implemented our own token generation and verification in this video we will go through how to leverage spring's oauth to resource server and its built-in classes to handle token notification and generation you'll see usage of the new spring security filter chain as well as using public and private keys for signing and verifying tokens we start off at the spring initializer website to create our spring project we'll add the mongodb oat to resource server spring web and lombok dependencies when ready hit generate and a zip file will be downloaded extract it and open it in intellij we'll start with our security configuration create a security package and a web security class at the configuration annotation enable global method security annotation with pre-post enabled and slf 4j annotation if you want to use a logger in this class now we define a security filter chain bean do we configure the slash api slash auth endpoints to be publicly accessible while every other endpoint must be accessed by authenticated clients the disabled csrf cores and springs http basic login and registration then we configure the oo2 resource server we specify to use jwt authentication and we customize it to use a converter that will convert the jwt token class to our own user class this will make it easier to use or retrieve the authenticated user from spring's security context we'll create the actual converter class in a moment lastly set the session policy to be stateless and configure the exception handling with the bare token entry point and access the night handler which all come from the oauth to resource server before we can create a jwt authentication converter we need to have a user class let's create that next create a document package and a user class add the document data required rx constructor and no arcs constructor annotations and implement the user details interface next we define the id username and password fields note the username and password variables are marked with non-null which means the required rx constructor will have these two variables as arguments lastly implement the required methods from the user details interface and return the correct values for each method while we're in this area we'll also add the user repo to enable user lookups create the repository package and user repository interface extend the manga repository of type user and string define two methods to return an optional user by username and another to return a boolean whether user exists by a given username now we create a jwt token to user converter class in the security package create a jwt to user converter class add the component annotation and implement the converter interface of type jwt and the username password authentication token make sure the converter import is from the spring framework package and then implement the convert method here we get the decoded jwt token this will be the access or the refresh token as we'll use the same converter for converting both tokens you can also use separate converters if you wish the user id is in the token subject so we'll create an empty user class and set the id coming from the token then we wrap the user and the jwt token in a username password authentication token so whenever you retrieve the authentication object from spring security context and you call the dot get principal method on it it will return the user object and when you call the dot get credential method on it it will return the jwt token note this is a barebones user object with just an id which is essentially what jwt is a stateless object where we avoid database calls but if you want the full user object here you'll have to make a database call to retrieve and set the full user object coming back to our web security class we inject the converter and configure oat to resource server to use it now before the resource server can verify tokens or issue them it needs to know what public and private keys to use what we'll do is read the public and private key supplied from files so let's create a key utils class in the security package add the component annotation the idea here is that the application will be supplied with file paths to the public and private keys and then it will read them in and create the necessary classes for token verification and issuing of tokens i'll define two variables which get their value from environment variables for the public and private keys used for access tokens [Music] while we're here i'll also create the same variables for the refresh token next i create a private key pair for the access and refresh tokens this will allow us to read from a file once and then store it in a variable then create four public methods to retrieve the public and private keys which are empty for now next we create the get access token keep your method here we check if the access token keeper variable is null if it is we use the get keypair method to read the keys from the specified file paths we'll create this method in a moment and finally return the access token keep here we do the same for getting the refresh token keep here now we can finish our public methods return get access token keeper dot get public and we need to cast it to an rsa public key for the get access token private key method call the dot get private method on the key pair and cast it to an rsa private key and do the same for the refresh token methods next let's define the getkeeper method which will take a public keypad and a private keypad has arguments define a keypair which will be populated later and returned then create a public key file and private key file from the file paths [Music] next we need to check if the public and private key files exist and if not we'll throw an exception [Music] if the files exist will create an rsa key factory then read the byte from the file then create an x509 encoded key spec using the bytes we read in and then use the factory to create the public key from the public key spec next i'll copy paste the three lines to read the private key from file we need to change the private key spec to be a pkcs8 encoded key spec and call the generate private method on the key factory then create a key pair from the public and private key and assign to the keypair variable also ensure the private key variable is a private key class and return the key pair next we'll wrap all the file reading code in the try catch block and we'll tell intellij to add the catch clauses then we'll collapse the cache blocks now we need to define the environment variables i will rename application.properties to application.yml open the file and define the access token private and public variables the value of these properties will be passed in through environment variables so we use the dollar sign curly braces syntax we'll do the same for the refresh token public and private keypads next i'll create an application dev.yml file to be used when the devspring profile is active and i'll define the environment variables here which in production would have been passed in at runtime in development mode we'll use public and private key files from our root folder we'll have a access refresh token keys folder and inside we'll have the public and private key files you can generate the public and private keys on the command line using ssh keygen but i'll show you how to do it in java to make it easier for development so what we'll do is when we detect that the public and private key files don't exist we will generate them in production we don't want to do that we want to control the keys used by the app and when the app is scaled to multiple instances you want each instance to use the same set of keys otherwise tokens issued by one instance are not valid in another instance because it uses a different set of keys so coming back to our key utils class we inject the environment variable and in the else block we're going to check which profile is active if it's the production profile then we throw the exception below the if else block we're going to generate our public and private keys we define the directory file instance then we check if the directory exists and if not then we create a directory then we create an rsa keypair generator we initialize it with a key size of 2048 and we call generate keypair on the generator and assign the result to the keypair variable at this point we have our public and private keys but only in memory next we want to write them to files so we can reuse the keys when the app restarts next we use a try with resources block to create a file output stream using the public keypad we create a x509 encoded key passing in the encoded result of the public key and finally we use the file output stream to write the encoded key spec to file i'll copy paste this to do the same for the private key change the key spec class and use the get private method on the key pair we'll want to wrap this code in a try catch block and collapse the catch clauses and lastly return the keypair variable so now in dev mode if the public and private keys don't exist they'll get generated and next time the app restarts it will read the previously generated keys but in production mode when the prod profile is active if the keys don't exist an exception will be thrown with our key utils in place we can now create the token decoder which will be used by the resource server to decode the access tokens open the web security class and define a jwt decoder beam i also add the primary annotation because we'll have another jwt decoder for the refresh tokens and we want the oa2 resource server to use the one for the access tokens then return a jwc decoder with public key inject the key utils class and call the get access token public key method and finally call the build method next we create a jwt encoder to create tokens here we create a jwk instance using an rsa keybuilder where we pass in the public and private key then create a jwk source and lastly return a new nimbus jwt encoder so that's the access token decoder and encoder the o2 resource server will automatically pick up the decoder and use it to verify access tokens and we'll use the encoder later to issue access tokens now we'll create the same beans for the refresh token and i replace the primer annotation with the qualifier to be able to reference this specific bean later on and change the code to use the refresh token public key for the decoder and do the same for the encoder the oo2 resource server will verify the access tokens but we also need a way to verify the refresh tokens for that we'll create a gwt authentication provider bin and add a qualifier to be safe create a new jwt authentication provider passing in the refresh token decoder we'll also set the data ability to user converter here and return the provider next we can start working on our endpoints create a web package and then an odd controller class add the rest controller and rest mapping annotations define a register post request which takes a sign of dto as the request body we don't have a sign of dto so let's create it create a dto package and the sign of dto class define a username and password variables and add the lumbag getter and setter annotations back in the odd controller import the sign of dto then we'll create a user object passing in the username and password then we'll want to use a user details manager to persist the user we don't have a user details manager so let's create that now create a service package and the user manager class add the service annotation and implement the user details manager interface we'll want to inject the user repo and a password encoder then implement the required methods from the interface the create user method takes a user details class but we know we're passing in a user document so we can cast the user argument to a user class and overwrite the password value with an encoded version of the password lastly use the repo to persist the user object i'll also implement the user exists method and load by username method coming back to the odd controller we persist the user then create an authentication instance using the username password authentication token then we will return the access and refresh tokens we'll need a token generator for that in the security package create a token generator class add the component annotation then inject the jwt encoder for the access token and refresh token note the jwt encoder for the access token has been marked as primary so when no qualifier is used it will be the access token encoder while for the refresh token encoder we need to use the qualifier to tell spring which beam to inject here first let's create a method to create an access token it will take an authentication argument then we get the user document using the authentication.getprinciple method then we create a time reference to this point in time then we create a jwt claim set which contains details about the access token to generate we set the token issuer the issue that date the expiration date which is five minutes from now we set the subject to be the user id and then we use the access token encoder to encode the claim set and return the token value then i create a similar method for creating a refresh token except i changed the expiration date to be 30 days you can externalize the expiration values to use environment variables if you wish we want to return the token values in an object containing both the access token and the refresh token let's create a token dto for that in the dto package create a token dto class and the user id access token and refresh token string variables and add together setter a large constructor and numark's constructor annotations back in the token generator class add a public create token method which returns a token dto and takes an authentication object as an argument for safety we ought to check to ensure the principal object of the authentication object is of a user type otherwise we'll throw an exception [Music] then we create an empty token dto in the if check add an implicit cast variable set the user id on the token detail from the user object and set the access token on the token dto now for the refresh token we'll do something different we'll have one endpoint for the token or a client must apply a refresh token to get a new access token what we'll do is check the reverse token expiry and if there are less than seven days of validity remaining we'll create a new refresh token and return it to the client otherwise we'll return the exact same refresh token that was passed in define a refresh token string then we check if the credential is a jwt type and implicitly cast it then i create a time reference now and get the duration between now and the refresh token expiry date then i check how many days are in that duration and if the remaining valid number of days is less than seven i create a new refresh token otherwise i reuse the refresh token passed in and if the credential is not a jwt type then i create a new refresh token finally set the reverse token value on the token dto and return the token dto back in the hot controller inject the token generator and return the response entity dot ok and use the token generator to create a token dto passing in the authentication object also we need to define the password encoder in the main application class define the bcrypt password encoder bean and add the enablemongo repos annotation we want to connect the app to a database before we can run it in the root of the project create a docker compose file and create the mongodb service specifying the required environment variables next open the application.yml file and add the spring date among the dburi property and we'll externalize the values here then open the application dev.yml file to provide the required environment variables in dev mode then run dockercompose.d to start the database container then start the application and we get an error so our spring app doesn't know anything about the different profiles we want to use so by default it uses the default profile open the application.yml file and set the spring profile active property and set the value to dev this will enable the dev profile now start the application and you shouldn't see any more errors now open postman and create a post request at the register endpoint set the body to raw json and define the username and password and execute the request it also helps to use the correct endpoint make sure it's api register execute the request and we get back the user id access token and refresh token now we can work on the login endpoint find the login endpoint which is a post request and accept a login dto body in the dto package create a login dto class define the username and password string variables and add together and set our annotations from lambok alright so now we need a way to verify the incoming username and password is valid for user for that we need an authentication provider go back to the web security class and define a dao authentication provider bin create a new dna authentication provider next inject the password encoder and the user details manager then set the password encoder and the user details manager on the provider and finally return the provider so this authentication provider will be used to verify if a username and password combination is valid against the user records in our database back in the hot controller inject the dna authentication provider and use the duo authentication provider to authenticate the incoming username and password if the authentication succeeds the returned authentication object will contain the user principle otherwise an error will be thrown next use the token generator to create an access and reverse token and return it [Music] now in postman execute a login request to the login endpoint [Music] and we get back to access and refresh tokens next we create a token endpoint that will be used to acquire new access tokens and when needed a new refresh token create a post request and will reuse the token detail for the body the client will need to supply a valid refresh token before a new access token can be issued before we issue a new access token we need to verify the refresh token for that we need to inject our jwt authentication provider for the refresh token [Music] then we use the token authentication provider to authenticate the incoming refresh token that's wrapped in a bare token authentication token [Music] if the refresh token is valid it is decoded the energy to user converter is executed converting the jwt token to a user object lastly we use the token generator passing in the authentication object to return a set of tokens in postman create a new post request to the token endpoint for the body use an object with the refresh token property and supply a valid refresh token that you can get from the login request execute the request and we get back an unexpected 401 response code to better understand what's happening i turn on debug logging for the spring security framework executing the request again we see that the refresh token is invalid in the logs i also want to add some logs for the qutils class so open it and add the slf4j annotation and add a log message for when the keys are loaded from files together with the actual file paths [Music] and add a log message when the keys are generated along with file paths [Music] start up and we can see the keys are loaded from files but the file paths are incorrect and also we can see the keys are generated in the root folder this is not what we want delete those keys back in the key tells class for the add value annotations we need to use the dollar sign curly braces syntax to read environment variables so update the four variables with the right syntax now when we start the app our log message confirms the keys are generated in the correct place now back in postman execute the login request to get a refresh token then execute the token request and we get back an unexpected response again looking at our log messages the refresh token is still invalid looking at the token generator we're actually using the wrong encoder for issuing refresh tokens so let's fix that by using the refresh token encoder now restart the app execute the login request again to get a new refresh token then use the refresh token to execute the token request and this time we get back a new access token and the same reverse token we get back the same refresh token because it has more than seven days left until expiry if it had less than seven days left we get back a new refresh token now let's actually put the odd to resource server in action and create some authenticated endpoints in the web package create a user controller and it would help if i can spell here add the usual rest controller and request mapping annotations inject a user repo and define a get request with a variable id add an empty pre-authorized annotation and define the method we can inject authenticated principle argument here and we'll use the ikeypath variable to look up users based on id next finish off the authorized logic where we compare the authenticated user id against the id path variable if the values are the same the request will be permitted otherwise the request will be denied this request will simply return a user object but we don't want to return the user document directly so we'll create a user dto to only return a subset of properties in the dto package create a user dto class add the builder and data annotations from lombok the builder annotation adds a builder to enable us to build the user dt object with builder style then i define the id and username string properties since that is all i want to return to the client then i define a convenient static method to create a user d2 object from a user document object and i use the builder to create the user dto object back in the user controller we use the repo to look up the user by id and then use the user dto convenience method to convert the user document to a user dto defined by id method returns an optional value so we need to decide what to do when there is no value i chose to throw an exception in that case now start the app in postman execute the login request to get an access token create a new get request and in the authorization tab for the type choose bearer token and paste access token value update the url point to the user endpoint and specify your user id execute the request and we get back a user dto now let's say we want to access another user's details by changing the user id in the url when executing the request we get back a 403 status code forbidden so that's our pre-authorized check at work and that's our backend essentially done now for the reverse token we're only checking if it's a valid refresh token there are no other checks such as checking if it's revoked which we implemented in previous video to have revoking capabilities you need to keep track of issue tokens and have a revoked flag to get details about the refresh token in the token request you can get access to the jwt token itself by calling the get credentials on the authentication object then you can perform additional checks against db if you wish and that's it for this video thanks for watching and see you in the next one
Info
Channel: Coding with Max
Views: 39,933
Rating: undefined out of 5
Keywords:
Id: FoyAvzU5fO0
Channel Id: undefined
Length: 42min 8sec (2528 seconds)
Published: Thu Jul 21 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.