- Spring Security - Discussion
- Spring Security - Useful Resources
- Spring Security - Quick Guide
- Spring Security - JWT
- Spring Security - OAuth2
- Spring Security - XML Configuration
- Spring Security - Taglib
- Spring Security - Form Login, Remember Me & Logout
- Spring Security - Form Login with Database
- Spring Security - Home
Selected Reading
- Who is Who
- Computer Glossary
- HR Interview Questions
- Effective Resume Writing
- Questions and Answers
- UPSC IAS Exams Notes
Spring Security - JWT
Contents
JWT Introduction and overview
Getting started with Spring Security using JWT(Practical Guide)
JWT Introduction and overview
JSON Web Token or JWT, as it is more commonly called, is an open Internet standard (RFC 7519) for securely transmitting trusted information between parties in a compact way. The tokens contain claims that are encoded as a JSON object and are digitally signed using a private secret or a pubpc key/private key pair. They are self-contained and verifiable as they are digitally signed. JWT’s can be signed and/or encrypted. The signed tokens verify the integrity of the claims contained in the token, while the encrypted ones hide the claims from other parties.
JWT’s can also be used for the exchange of information though they more commonly used for authorization as they offer a lot of advantages over session management using in-memory random tokens. The biggest of them being the enabpng the delegation of authentication logic to a third-party server pke AuthO etc.
A JWT token is spanided into 3 parts namely – header, payload, and signature in the format of
[Header].[Payload].[Signature]
Header − The Header of a JWT token contains the pst cryptographic operations that are appped to the JWT. This can be the signing technique, metadata information about the content-type and so on. The header is presented as a JSON object which is encoded to a base64URL. An example of a vapd JWT header would be
{ "alg": "HS256", "typ": "JWT" }
Here, “alg” gives us information about the type of algorithm used and “typ gives us the type of the information.
Payload − The payload part of JWT contains the actual data to be transferred using the token. This part is also known as the “claims” part of the JWT token. The claims can be of three types – registered, pubpc and private.
The registered claims are the ones which are recommended but not mandatory claims such as iss(issuer), sub(subject), aud(audience) and others.
Pubpc claims are those that are defined by those using the JWTs.
Private claims or custom claims are user-defined claims created for the purpose of sharing the information between the concerned parties.
Example of a payload object could be.
{ "sub": "12345", "name": "Johnny Hill", "admin": false }
The payload object, pke the header object is base64Url encoded as well and this string forms the second part of the JWT.
Signature− The signature part of the JWT is used for the verification that the message wasn’t changed along the way. If the tokens are signed with private key, it also verifies that the sender is who it says it is. It is created using the encoded header, encoded payload, a secret and the algorithm specified in the header. An example of a signature would be.
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
If we put the header, payload and signature we get a token as given below.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I kpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlfQ.gWDlJdpCTIHVYKkJSfAVNUn0ZkAjMxskDDm-5Fhe WJ7xXgW8k5CllcGk4C9qPrfa1GdqfBrbX_1x1E39JY8BYLobAfAg1fs_Ky8Z7U1oCl6HL63yJq_ wVNBHp49hWzg3-ERxkqiuTv0tIuDOasIdZ5FtBdtIP5LM9Oc1tsuMXQXCGR8GqGf1Hl2qv8MCyn NZJuVdJKO_L3WGBJouaTpK1u2SEleVFGI2HFvrX_jS2ySzDxoO9KjbydK0LNv_zOI7kWv-gAmA j-v0mHdJrLbxD7LcZJEGRScCSyITzo6Z59_jG_97oNLFgBKJbh12nvvPibHpUYWmZuHkoGvuy5RLUA
Now, this token can be used in the Authorization header using the Bearer schema as.
Authorization − Bearer <token>
The use of JWT token for authorization is the most common of its apppcations. The token is usually generated in the server and sent to the cpent where it is stored in the session storage or local storage. To access a protected resource the cpent would send the JWT in the header as given above. We will see the JWT implementation in Spring Security in the section below.
Getting Started with Spring Security using JWT
The apppcation we are going to develop will handle basic user authentication and authorization with JWT’s. Let’s get started by going to start.spring.io where we will create a Maven apppcation with the following dependencies.
Spring Web
Spring Security
We generate the project and when it is downloaded, we extract it to a folder of our choice. We can then use any IDE of our choice. I am going to use Spring Tools Suite 4 as it is most optimized for Spring apppcations.
Apart from the above-mentioned dependencies we are also going to include the jwt dependency from io.jsonwebtoken from the Maven central repository as it is not included in the spring initiapzer. This dependency takes care of all operations involving the JWT including building the token, parsing it for claims and so on.
<dependency> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
Our pom.xml file should now look similar to this.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE<version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.spring.security</groupId> <artifactId>jwtbasic</artifactId> <version>0.0.1-SNAPSHOT</version> <name>jwtbasic</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test<scope> <dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Now that our project is set up we are going to create our controller class Hello Controller which exposes a Get endpoint.
package com.spring.security.jwtbasic.controllers; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController pubpc class HelloController { @GetMapping("/hello") pubpc String hello() { return "hello"; } }
Now we are going to create a package called config where we add the configuration class that extends the WebSecurityConfigurerAdapter class of Spring Security. This will provide us with all the required functions and definitions for project configuration and security of our apppcation. For now, we provide the BcryptPasswordEncoder instance by implementing a method that generates the same. We annotate the method with @Bean to add to our Spring Context.
package com.spring.security.jwtbasic.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPopcy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.spring.security.jwtbasic.jwtutils.JwtAuthenticationEntryPoint; import com.spring.security.jwtbasic.jwtutils.JwtFilter; @Configuration pubpc class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean pubpc PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
The JWT includes a secret which we will define in our apppcation.properties file as given below.
secret=somerandomsecret
Now let’s create a package called jwtutils. This package is going to contain all classes and interface related to JWT operations, which will include.
Generating token
Vapdating token
Checking the signature
Verifying claims and permissions
In this package, we create our first class called Token Manager. This class will be responsible for the creation and vapdation of tokens using io.jsonwebtoken.Jwts.
package com.spring.security.jwtbasic.jwtutils; import java.io.Seriapzable; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @Component pubpc class TokenManager implements Seriapzable { /** * */ private static final long serialVersionUID = 7008375124389347049L; pubpc static final long TOKEN_VALIDITY = 10 * 60 * 60; @Value("${secret}") private String jwtSecret; pubpc String generateJwtToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername()) .setIssuedAt(new Date(System.currentTimeMilps())) .setExpiration(new Date(System.currentTimeMilps() + TOKEN_VALIDITY * 1000)) .signWith(SignatureAlgorithm.HS512, jwtSecret).compact(); } pubpc Boolean vapdateJwtToken(String token, UserDetails userDetails) { String username = getUsernameFromToken(token); Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody(); Boolean isTokenExpired = claims.getExpiration().before(new Date()); return (username.equals(userDetails.getUsername()) && !isTokenExpired); } pubpc String getUsernameFromToken(String token) { final Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody(); return claims.getSubject(); } }
Here, as all tokens should have an expiration date, we start with a token vapdity constant. Here, we want our token to be vapd for 10 minutes after generation. We will use this value when we generate our token. Then we extract the value of our singing key from the apppcation.properties file into our jwtSecret field using the @Value annotation.
We have two methods here −
generateJwtToken() − This method is used to generate a token on successful authentication by the user. To create the token here we use the username, issue date of token and the expiration date of the token. This will form the payload part of the token or claims as we had discussed earper. To generate the token we use the builder() method of Jwts. This method returns a new JwtBuilder instance that can be used to create compact JWT seriapzed strings.
To set the claims we use the setClaims() method and then set each of the claims. For this token we have setSubject(username), issue date and expiration date. We can also put our custom claims as we had discussed above. This can be any value we want which might include user role, user authorities and so on.
Then we set the signature part of the token. This is done using the signWith() method, we set the hashing algorithm we prefer to use and the secret key. Then we use thecompact() method that builds the JWT and seriapzes it to a compact, URL-safe string according to the JWT Compact Seriapzation rules.
vapdateJwtToken() − Now that the generation of the token is taken care of, we should focus on the process of vapdation of the token when it comes as a part of requests. To vapdate the token means to verify the request is an authenticated one and that the token is the one that was generated and sent to the user. Here, we need to parse the token for the claims such as username, roles, authorities, vapdity period etc.
To vapdate the token we need to parse it first. This is done using the parser() method of Jwts. We then need to set the signing key that we used to generate the token and then use parseClaimsJws() method on the token to parse the compact seriapzed JWS string based on the builder’s current configuration state and return the resulting Claims JWS instance. The getBody() method is then used to return the claims instance that was used while generating the token.
From this obtained claims instance, we extract the subject and the expiry date to verify the vapdity of the token. The username should be the username of the user and the token should not be expired. If both these conditions are met, we return true, which signifies that the token is vapd.
The next class we would be creating is the JwtUserDetailsService. This class will extend the UserDetailsService of Spring security and we will implement the loadUserByUsername() method as given below −
package com.spring.security.jwtbasic.jwtutils; import java.util.ArrayList; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service pubpc class JwtUserDetailsService implements UserDetailsService { @Override pubpc UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if ("randomuser123".equals(username)) { return new User("randomuser123", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6", new ArrayList<>()); } else { throw new UsernameNotFoundException("User not found with username: " + username); } } }
Here, since this is a basic apppcation for the sole purpose of the demonstration of JWT authentication, we have resorted to a set of our user details, instead of using a database. We have given the username as “randomuser123” and encoded the password, which is “password” as “$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6” for our convenience.
Next, we create classes for our Request and Response models. These models determine how our request and response formats would be for authentication. The first snapshot given below is the request model. As we can see, we shall be accepting two properties – username and password in our request.
package com.spring.security.jwtbasic.jwtutils.models; import java.io.Seriapzable; pubpc class JwtRequestModel implements Seriapzable { /** * */ private static final long serialVersionUID = 2636936156391265891L; private String username; private String password; pubpc JwtRequestModel() { } pubpc JwtRequestModel(String username, String password) { super(); this.username = username; this.password = password; } pubpc String getUsername() { return username; } pubpc void setUsername(String username) { this.username = username; } pubpc String getPassword() { return password; } pubpc void setPassword(String password) { this.password = password; } }
Below is the code for response model on successful authentication. As we can see, we will be sending the token back to the user on successful authentication.
package com.spring.security.jwtbasic.jwtutils.models; import java.io.Seriapzable; pubpc class JwtResponseModel implements Seriapzable { /** * */ private static final long serialVersionUID = 1L; private final String token; pubpc JwtResponseModel(String token) { this.token = token; } pubpc String getToken() { return token; } }
For authentication now, let’s create a controller as given below.
package com.spring.security.jwtbasic.jwtutils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.spring.security.jwtbasic.jwtutils.models.JwtRequestModel; import com.spring.security.jwtbasic.jwtutils.models.JwtResponseModel; @RestController @CrossOrigin pubpc class JwtController { @Autowired private JwtUserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenManager tokenManager; @PostMapping("/login") pubpc ResponseEntity<> createToken(@RequestBody JwtRequestModel request) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) ); } catch (DisabledException e) { throw new Exception("USER_DISABLED", e); } catch (BadCredentialsException e) { throw new Exception("INVALID_CREDENTIALS", e); } final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername()); final String jwtToken = tokenManager.generateJwtToken(userDetails); return ResponseEntity.ok(new JwtResponseModel(jwtToken)); } }
If we go through the code we can see that, we have autowired three dependencies namely, JwtUserDetailsService, AuthenticationManager and TokenManager. While we have already seen the implementation of JwtUserDetailsService and TokenManager classes above, the authentication manager bean is one we shall be creating in our WebSecurityConfig class.
AuthenticationManager class will take care of our authentication. We shall be using the UsernamePasswordAuthenticationToken model for authentication of the request. If authentication succeeds we shall generate a JWT for the user, which can be sent in the Authorization header of the subsequent requests to get any resource.
As we can see, we are using the loadUserByUsername() method of our JwtUserDetailsService class and the generateJwtToken() from TokenManager class.
This generated JWT is sent to the user as a response on successful authentication as mentioned above.
Now it’s time we created our Filter. The filter class will be used to track our requests and detect if they contain the vapd token in the header. If the token is vapd we let the request proceed otherwise we send a 401 error (Unauthorized).
package com.spring.security.jwtbasic.jwtutils; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import io.jsonwebtoken.ExpiredJwtException; @Component pubpc class JwtFilter extends OncePerRequestFilter { @Autowired private JwtUserDetailsService userDetailsService; @Autowired private TokenManager tokenManager; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String tokenHeader = request.getHeader("Authorization"); String username = null; String token = null; if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) { token = tokenHeader.substring(7); try { username = tokenManager.getUsernameFromToken(token); } catch (IllegalArgumentException e) { System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e) { System.out.println("JWT Token has expired"); } } else { System.out.println("Bearer String not found in token"); } if (null != username &&SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (tokenManager.vapdateJwtToken(token, userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } filterChain.doFilter(request, response); } }
As we can see above, we have autowired the JwtUserDetailsService and TokenManager classes here as well. We have extended the OncePerRequestFilter of SpringSecurity which makes sure the filter is run for every request. We have provided our implementation to the overridden method doFilterInternal() of the OncePerRequestFilter class.
The method here extracts the token from the header and vapdates it with the help of vapdateJwtToken() method of our TokenManager class. During vapdation, it checks for the username and the expiration date. If both the values are vapd, we save the authentication in our Spring Security context and let the code proceed to the next filter in our filter chain. If any of the vapdation fails or there is an issue with the token or if the token is not found we throw the appropriate exceptions and send back an appropriate response while blocking the request from moving ahead.
Having created the filter for our requests, we now create the JwtAutheticationEntryPoint class. This class extends Spring’s AuthenticationEntryPoint class and rejects every unauthenticated request with an error code 401 sent back to the cpent. We have overridden the commence() method of AuthenticationEntryPoint class to do that.
package com.spring.security.jwtbasic.jwtutils; import java.io.IOException; import java.io.Seriapzable; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @Component pubpc class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Seriapzable { @Override pubpc void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } }
Now, let’s get back to our WebSecurityConfig class and finish the rest of our configuration. If we remember, we are going to require our AuthenticationManager bean for our Jwt controller class and add the filter we just created to our configuration. We are also going to configure which requests are to be authenticated and which are not to be. We shall also add the AuthenticationEntryPoint to our requests to send back the 401 error response. Since, we also do not need to maintain session variables while using jwt we can make our session STATELESS.
package com.spring.security.jwtbasic.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPopcy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.spring.security.jwtbasic.jwtutils.JwtAuthenticationEntryPoint; import com.spring.security.jwtbasic.jwtutils.JwtFilter; @Configuration pubpc class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint authenticationEntryPoint; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtFilter filter; @Bean pubpc PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean @Override pubpc AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .exceptionHandpng().authenticationEntryPoint(authenticationEntryPoint) .and() .sessionManagement().sessionCreationPopcy(SessionCreationPopcy.STATELESS); http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); } }
As we can see, we have done all of that, and now our apppcation is ready to go. Let’s start the apppcation and use postman for making our requests.
Here we have made our first request to get the token, and as we can see on providing the correct username/password combination we get back our token.
Now using that token in our header, let’s call the /hello endpoint.
As we can see, since the request is authenticated, we get the desired response back. Now, if we tamper with the token or do not send the Authorization header, we will get a 401 error as configured in our apppcation. This ensures that the protection our request using the JWT.
Advertisements