As we are moving towards Micro service based architecture most of our API are required to be state less and adoption of REST API is at peak. so to authorise our request we have one globally accepted method is through JWT. So JWT is Json  based web token use to transfers claims securely between two parties. and spring security provides methods and configuration to achieve easily,but when our need customisation the   we trend to add more  boilerplate code and code readability and complexity becomes more deeper, so instead we can focus on what we need and how we need by writing our custom logic. so in this tutorial i will be using springsandwich library, but there is another way to implement authorisation you can pick which ever is convenient to you [Authorisation through custom annotation].

Step 1. Adding required dependency.

  1. Spring boot web starter package
    As we know spring boot web starter would makes easily start web application providing necessary configuration.
  2. Spring Sandwich
    This is a custom interceptor library which is far more simpler than spring interceptor or spring aspects you read more on in this link
  3. Jose Jwt plugin
    This plugin will help to endoce and decode the jwt token

So dependency maven list looks like this

<dependencies>

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.kastkode</groupId>
            <artifactId>springsandwich</artifactId>
            <version>[1.0.2,)</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>

        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <dependency>
            <groupId>org.bitbucket.b_c</groupId>
            <artifactId>jose4j</artifactId>
            <version>0.6.5</version>
        </dependency>



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Step 2. Let's create the component which will create JWT token and parse JWT token

@Component
public class JWTBuilder {
    @Value("${jwt.issuer}")
    private String jwtIssuer;
    @Value("${jwt.secret}")
    private String jwtSecret;
    @Value("${jwt.expiry}")
    private Float jwtExpiry;
    RsaJsonWebKey rsaJsonWebKey;

    public JWTBuilder() {

    }

    public String generateToken(String usersId,String roles) {
        try {
            JwtClaims jwtClaims = new JwtClaims();
            jwtClaims.setIssuer(jwtIssuer);
            jwtClaims.setExpirationTimeMinutesInTheFuture(jwtExpiry);
           

            jwtClaims.setAudience("ALL");
            jwtClaims.setStringListClaim("groups", roles);
            jwtClaims.setGeneratedJwtId();
            jwtClaims.setIssuedAtToNow();
            jwtClaims.setSubject("AUTHTOKEN");
            jwtClaims.setClaim("userId", usersId);
            JsonWebSignature jws = new JsonWebSignature();
            jws.setPayload(jwtClaims.toJson());
            jws.setKey(rsaJsonWebKey.getPrivateKey());
            jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId());
            jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

            return jws.getCompactSerialization();
        } catch (JoseException e) {
            e.printStackTrace();
            return null;

        }

    }

    public JwtClaims generateParseToken(String token)  {
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime()
                .setSkipSignatureVerification()
                .setAllowedClockSkewInSeconds(60)
                .setRequireSubject()
                .setExpectedIssuer(jwtIssuer)
                .setExpectedAudience("ALL")
                .setExpectedSubject("AUTHTOKEN")
                .setVerificationKey(rsaJsonWebKey.getKey())
                .setJwsAlgorithmConstraints(
                        new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST,
                                AlgorithmIdentifiers.RSA_USING_SHA256))
                .build();
        try
        {
            JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
            return jwtClaims;
        } catch (InvalidJwtException e) {
            try {
                if (e.hasExpired())
                {
                    throw new AuthException("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
                }
                if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
                {
                    throw new AuthException("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
                }
                throw new AuthException(e.getMessage());
            } catch (MalformedClaimException innerE) {
                throw new AuthException("invalid Token");
            }

        }
    }

    @PostConstruct
    public void init() {
        try {
            rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
            rsaJsonWebKey.setKeyId(jwtSecret);
        } catch (JoseException e) {
            e.printStackTrace();
        }
    }
}

basically this component is seperated in three parts first parth is generating the rsaKey used to encryt the certificate to check its authenticity. and generateToken is use to create JWT token and generateParseToken to decode the token and check its authenticity

Step 3. Writing SpringSandwich interceptor to parse the token.

@Component
public class AuthHandler implements BeforeHandler {

    Logger logger = LoggerFactory.getLogger(AuthHandler.class);
    
    @Autowired
    JWTBuilder jwtbuilder
    

    @Override
    public Flow handle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, String[] flags) throws Exception {
        logger.debug(request.getMethod() + "request is executing on" + request.getURI());
        String token = request.getHeader("Authorization");

        if( token == null) {
            throw new RuntimeException("Auth token is required");
        }
        
        JWTclaims claims = jwtbuilder.generateParseToken(token)
        request.setAttribute(userId,claims.getClaimValue("userId").toString())
        //do the db call and check is the user is still valid, avoided to make tutorial simple.
        return Flow.CONTINUE;
    }
}

So this Interceptor will read token from header and parse it, if the the token is expired or not valid token exception is thrown and http request is with held.

Step 4. Writing dummy controller to generate token and to authorise the request.

Class Controller {
...

@Autowired
JWTBuilder jwtbuilder;
    
@GetMapping(path="/login")
@Before( @BeforeElement(ConsoleLogger.class))
public String login() {
    return jwtbuilder.generateParseToken("test","admin");
}

@GetMapping(path="/authorise", headers = {"Authorization"})
@Before( @BeforeElement(AuthHandler.class))
public String test() {
    ...
}
...
}

this controller have two dummy api which is straight forward one generates random token for user, and one parse the token for authenticity of the token.

Step 4. Instruct spring boot use spring to use the custom interceptor in main application class.

@ComponentScan(basePackages = {"com.kastkode.springsandwich.filter", "com.your-app-here.*"})
    public class Main { ... }

please note com.your-app-here.* to replace to your application's package

use your post man to trigger the apis and check the result, this tutorial is very straight forward, by eliminating spring security we can implement custom rules and simplify the flow of login and authorisation, which make code easier.