In the previous tutorial we had used username/password from a database to secure our application. This tutorial is in continuation of the same previous tutorial where we would implement JWT to the same application. If you have not gone through that, I would suggest you to first see that.
So I had renamed the same application to “spring-security-jwt”. It is just an copy paste of the “spring-security-password” application. I added below libraries to the same application:
implementation 'io.jsonwebtoken:jjwt-api:0.10.5' implementation 'io.jsonwebtoken:jjwt-impl:0.10.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.5'
So the final dependencies section will look something like below:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'io.jsonwebtoken:jjwt-api:0.10.5' implementation 'io.jsonwebtoken:jjwt-impl:0.10.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.5' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' }
Create a util class- MyJwtUtil.java and add some standard methods like createToken(), validateToken(), etc.. like below :
package com.heapsteep.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Service public class MyJwtUtil { private String secret = "HeapsteepIsAveryGoodBloggingSiteForJavaTechStack"; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } public String generateToken(String username) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, username); } private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }
Create a User Transfer Object: UserTO.java:
@Data @AllArgsConstructor @NoArgsConstructor public class UserTO { private String userName; private String password; }
In MySecurityConfig.java add a AuthenticationManager Bean by overriding authenticationManagerBean() method:
@Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
To the MyController.java class add below endpoint to generate the JWT token:
@PostMapping("/authenticate") public String generateToken(@RequestBody UserTO userTO) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(userTO.getUserName(), userTO.getPassword()) ); } catch (Exception ex) { throw new Exception("inavalid username/password"); } return jwtUtil.generateToken(userTO.getUserName()); }
In the above method we are doing 2 steps – first authenticating the credentials and if it succeeds then only generating token.
Now we have to disable the above /authenticate endpoint from Spring Security config so that everyone should be able to access it. So, add below method in MySecurityConfig.java:
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests().antMatchers("/authenticate") .permitAll().anyRequest().authenticated(); }
Lets now run our application and see whether we are able to generate JWT token or not.
Run using gradle bootrun.
Open a postman and do a POST call to this URL: http://localhost:8080/authenticate
Send the below JSON request:
{ "userName": "Prasanna", "password": "1234" }
We should get the generated JWT token.
If we take this JWT key and put it here: jwt.io, we could see something like below:
Put the secret key in the “VERIFY SIGNATURE” and it will validate the signature.
Now, lets call our existing endpoints and see whether we are able to access that using JWT token.
So, lets do a GET call to http://localhost:8080 passing below values in Headers:
Content-Type: application/json
Authorization: Bearer <JWT token>
You will get 403 forbidden error – Access Denied.
Because Spring does not understand what we are giving as part of the Authorization header.
So we have to tell Spring to extract credentials from the token and validate it. For that we have to add an additional layer, basically a filter before it reaches the controller class.
Create a new filter class like – MyJWTFilter.java. Extend it from OncePerRequestFilter and override the doFilterInternal(request, response, filterChain) method. Annotate this class as @Component.
In the above method we are doing the below things:
1. Extracting the token from the Authorization header.
2. Extracting the User Name from the token.
3. Fetch the User Details using the User Name.
4. Validates the token.
5. If everything is Ok then we are setting it to SecurityContext object.
6. Call the doFilter() method.
package com.heapsteep.filter; import com.heapsteep.service.MyUserDetailsService; import com.heapsteep.util.MyJwtUtil; 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 javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class MyJWTFilter extends OncePerRequestFilter { @Autowired MyJwtUtil myJwtUtil; @Autowired MyUserDetailsService myUserDetailsService; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String authorizationHeader = httpServletRequest.getHeader("Authorization"); String token = null; String userName = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { token = authorizationHeader.substring(7); userName = myJwtUtil.extractUsername(token); } if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = myUserDetailsService.loadUserByUsername(userName); if (myJwtUtil.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } filterChain.doFilter(httpServletRequest, httpServletResponse); } }
Now we have to register your filter to your Security Config class by adding the below statements:
@Autowired MyJWTFilter jwtFilter; //below code snippet is a partial one: http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
Next we have to enable the Session Policy which is Stateless. Actually JWT follows Stateless session mechanism by not saving the client info neither in server or browser cookies.
P.S.: Below statement is an incomplete one. I had mentioned only the newly added code in the MySecurityConfig.java:
//Below statement is an incomplete one. I had mentioned only the newly added code: .and().exceptionHandling().and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
Start the application by: gradle bootrun
Now you will be able to access the endpoints successfully !!..
The source code of this project: https://github.com/heapsteep/spring-security-jwt