FC
FACADE
CODE
Lesson 28 of 28
FC
FACADE
CODE

Learn Spring Security

Contents

01. Bootstrap The Application
1. Introduction
2. Install Spring Security
3. Enable Basic Auth
4. Authentication with AppUser
5. Password Encoder
02. Web Layer Security - RBAC
6. Permit Public APIs
7. Role Based Authorization
8. Disable CSRF
9. Current Authenticated User
03. Web Layer Security - PBAC
10. Permission Based Authorization
11. Define Permissions
12. Assign Permissions
13. Remove Role Based Access
04. Service Layer Security
14. PreAuthorize
15. PostAuthorize
16. Authorize Using Spring Beans
05. Domain Object Instance Security
17. Domain Object Instance Security
18. PermissionEvaluator Interface
19. PermissionEvaluator Strategy
20. DB Backed UserDetailsService
06. Token Based Authentication
21. Basic Authentication Revisited
22. Generate Token
23. Persist Token
24. Verify Token
25. Invalidate Token
07. Token Based Authentication and Authorization
26. JSON Web Token
27. Generate JWT
28. Verify JWT

Verify JWT

As JWT is self-contained with the Subject information to whom the token is issued, we do not need to associate the token with the AppUser record in the database. Because of which we no longer need to identify the user from the database as well. All we need is just to verify the signature using the SecretKey and parse the JWT to construct the Authentication object as authenticated principal.

Let's update TokenVerificationFilter, by replacing the existing logic to identify the AppUser from the database using opaque token, with the below code:


@Override  
protected void doFilterInternal(HttpServletRequest request, 
            HttpServletResponse response, 
            FilterChain filterChain) throws ServletException, IOException {
    String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

    if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
        String accessToken = authorizationHeader.replace("Bearer ", "");
        // Parse JWT using the SecretKey
        Claims claims = Jwts.parserBuilder()
            .setSigningKey(jwtConfig.getSecretKey())
            .build()
            .parseClaimsJws(accessToken)
            .getBody();

        // Check if JWT has expired
        if (claims.getExpiration().after(new Date())) {
            Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(
                    claims.getSubject(), null, this.getAuthorities(claims)
            );
            authenticationFacade.setAuthentication(authentication);
        }
    }

    filterChain.doFilter(request, response);
}

private List<GrantedAuthority> getAuthorities(Claims claims) {
    return ((List<String>) claims.get("authorities")).stream()
        .map(SimpleGrantedAuthority::new)
        .collect(Collectors.toList());
}

Jwts provides another Builder class JwtParserBuilder to build JwtParser object. We are calling parseClaimsJws() method on JwtParser which accepts SecretKey to verify the signature and parse the content of JWT.

Note

You may notice another parser method parseClaimsJwt() in JwtParser. But we are using parseClaimsJws() because signed JWT tokens are called JWS (not JWT), and in our case we want to parse the JWS signed with the SecretKey.

The parsed JWT body has the information we saw earlier in the decoded payload data in the JWT official page. Now instead of identifying the user and his authorities from the database, we can get the same from the JWT payload. But before creating Authentication object form the JWT payload we have to check if the token has expired.

Tidy up and Test

We can remove the token and tokenExpiryTime from the AppUser entity as we are not using it anymore. We can also tidy up some of the code related to opaque token which are no more relevant with JWT.

  • Remove invalidateToken() from AuthenticationController and AuthenticationService.
  • Remove deleteToken() from UserService.
  • Remove pre-authorize condition defined for deleteToken() in SecurityConstants.
  • Remove loadUserByToken() from DbUserDetailsService.
  • Remove findByTokenAndTokenExpiryTimeGreaterThan() from AppUserRepository.

Restart the application and get the JWT generated for Admin user credentials by sending a POST request to GenerateToken API.

JWT generated for Admin user credentials on successful authentication
JWT generated for Admin user credentials on successful authentication

Now choose the Authorization type as Bearer Token, and paste the generated JWT in the Token Field. If we send a GET request to ListStudents API with JWT as the Bearer Token in the Authorization header, TokenVerificationFilter will verify the signature of JWS and parse the content to create Authentication object as an authenticated principal. Rest of the token-based authentication process remains the same as with our opaque token.

List of students response for the authorized JWT issed for Admin user
List of students response for the authorized JWT issed for Admin user

Similarly you can test for all the other protected resources for all the users with their respective JWT.

Conclusion

We reached the epic conclusion of this course. I hope you have got a better knowledge on how to secure every layer of an application from the scratch using some of the widely used concepts in Spring Security.

I will update this course constantly as and when we get new releases in Spring Security. Please reach out to me in email for any improvements or additions. My email address is email [at] this website name.

Previous