It has been a long time since i posted any java related blog post. Today it is a time i came up from my busy schedule. So in this post i am just only going to demonstrate how to add JWT authentication for small boot application which will use redis for saving its user’s session and information. There are many way we can use session but lets try this time with Redis as primary source for session storage.

I have my redis server in my VMWare which is opened in default port  6379. So now all i need is small spring boot application to get started. Lets start a new project with clean maven project and some dependencies essential for making JWT Token based session using redis.

1. Project Create

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.7.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.7.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
</dependencies>

Here we have just added <parent> for making the project to inherit all the stuffs from the spring-boot-starter-parent. So now we have added spring-boot-starter-web, spring-boot-starter-data-redis, spring-security, jwt, jedis(redis) and lombok in our project which are essential dependency for starting our spring boot rest application with redis support.

The first thing we do is Create main Application file which will help the Spring Boot application to start.

@SpringBootApplication
@EnableAutoConfiguration
public class Application {
    
    
    //... Stuffs to be added ...
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
}

So this is right now bare server with built-in spring security. Now lets create a small User resource which authenticated user can only view them.

2. Redis Configuration

This configuration is actually dependent on application.properties which contains the key-value map of redis params.

application.properties

redis.host = localhost
redis.port = 6379
redis.pass =

These values will be extracted from spring to configure redis connection and make ConnectionFactory available within an application.

RedisConfig.java

@Configuration
public class RedisConfig {

    @Value("${redis.host}")
    private String redisHost;

    @Value("${redis.port}")
    private Integer redisPort;

    @Value("${redis.pass}")
    private String redisPass;

    @Bean
    @Primary
    JedisConnectionFactory jedisConnectionFactory() throws Exception {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName(redisHost);
        factory.setPort(redisPort);
        if (redisPass != null) {
            factory.setPassword(redisPass);
        }
        factory.setUsePool(true);

        return factory;
    }

    @Bean
    RedisTemplate< String, Object> redisTemplate() throws Exception {
        final RedisTemplate< String, Object> template = new RedisTemplate< String, Object>();
        template.setConnectionFactory(jedisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());

        template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        return template;
    }
}

So now all the redis part of configuration is done . There are templates and connection factory in this RedisConfig.java. Templates are used for saving and extracting the complex data in redis and connection factory is for connection pooling of redis.

 

3. JWT Implementation

For making the JWT to work we need to add two filters on every request incoming to server. The first one would be for authenticating user with their credentials (user, password) and another would be for validating their JWT token and authorizing the requested resource. But before moving to those filter lets start creating a service which helps us to generate JWT token for us and validate that token against the redis.

TokenAuthenticationService.java

public class TokenAuthenticationService {

    private RedisService service;

    private long EXPIRATIONTIME = 1000 * 60 * 60; // 1 hr

    private String secret;

    private String tokenPrefix = "Bearer";

    private String headerString = "Authorization";

    public TokenAuthenticationService(RedisService service) {
        this.service = service;
        secret = Sha512DigestUtils.shaHex(System.getenv("ENC_KEY"));
    }

    public void addAuthentication(HttpServletResponse response, AuthenticationTokenImpl auth) {
        // We generate a token now.
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", auth.getPrincipal());
        claims.put("hash", auth.getHash());
        String JWT = Jwts.builder()
                .setSubject(auth.getPrincipal().toString())
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
        response.addHeader(headerString, tokenPrefix + " " + JWT);
    }

    public Authentication getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(headerString);
        if (token == null) {
            return null;
        }
        //remove "Bearer" text
        token = token.replace(tokenPrefix, "").trim();

        //Validating the token
        if (token != null && !token.isEmpty()) {
            // parsing the token.`
            Claims claims = null;
            try {
                claims = Jwts.parser()
                        .setSigningKey(secret)
                        .parseClaimsJws(token).getBody();

            } catch ( Exception e) {
                return null;
            }
            
            //Valid token and now checking to see if the token is actally expired or alive by quering in redis.
            if (claims != null && claims.containsKey("username")) {
                String username = claims.get("username").toString();
                String hash = claims.get("hash").toString();
                SessionUser user = (SessionUser) service.getValue(String.format("%s:%s", username,hash), SessionUser.class);
                if (user != null) {
                    AccessTokenBasedAuth auth = new AccessTokenBasedAuth(user.getUsername(), Collections.emptyList());
                    auth.setDetails(user);
                    auth.authenticate();
                    return auth;
                } else {
                    return new UsernamePasswordAuthenticationToken(null, null);
                }

            }
        }
        return null;
    }
}

If we look into addAuthentication() there we can see new JWT token is generated using the SHA encrypted password , the password is actually pulled from the system environment variable. Now for the authenticating part we are just using standard header format “Authorization: Bearer aawd383ec9930k… ” .  It just checks if there exist any record matching with the key (user:hash)  . Right now user can login with many session but in future we can do those restriction or view all the session and delete any which user wants to. So typically this service class will just create JWT Token and also validates the token which was sent on request payload header.

There is still a need of authentication part which really validates the user’s credential so lets create the part of authentication .

AuthenticationProviderImpl.java

public class AuthenticationProviderImpl implements org.springframework.security.authentication.AuthenticationProvider {

    private RedisService service;

    public AuthenticationProviderImpl(RedisService service) {
        this.service = service;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getPrincipal() + "";
        String password = authentication.getCredentials() + "";

        if (username == null || username.length() < 5) {
            throw new BadCredentialsException("Username not found.");
        }
        if (password.length() < 5) {
            throw new BadCredentialsException("Wrong password.");
        }

        //Right now just authenticate on the basis of the user=pass
        if (username.equalsIgnoreCase(password)) {
            SessionUser u = new SessionUser();
            u.setUsername(username);
            u.setCreated(new Date());
            AuthenticationTokenImpl auth = new AuthenticationTokenImpl(u.getUsername(), Collections.emptyList());
            auth.setAuthenticated(true);
            auth.setDetails(u);
            service.setValue(String.format("%s:%s", u.getUsername().toLowerCase(), auth.getHash()), u, TimeUnit.SECONDS, 3600L, true);
            return auth;
        } else {

        }
        return null;
    }

    @Override
    public boolean supports(Class<?> type) {
        return type.equals(UsernamePasswordAuthenticationToken.class);
    }

}

This class will authenticate the request and create new session for user then saves that user’s data in redis for making it available for certain time interval i.e. session time. The SessionUser is the object that we save inside of redis with it’s key formatted as “username:hash” . So if you are wondering what hash() really is then its time to show you small snippet of AuthenticationTokenImpl.java. This class is just a subclass of AbstractAuthenticationToken which adds some extra parameter to be able to handle on.

 

AuthenticationTokenImpl.java

public class AuthenticationTokenImpl extends AbstractAuthenticationToken{
    
    //...other codes
    
    public String getHash() {
        return DigestUtils.md5DigestAsHex(String.format("%s_%d", username, ((SessionUser) getDetails()).getCreated().getTime()).getBytes());
    }
    
}

 

4. Spring Security and Request Filters

The master piece of all is the spring security which maintains all our resources to be secured and only allow after there is a valid token while doing a request. When it comes for restricting access resource in spring it comes quiet handy to manage them i.e. WebSecurityConfigurerAdapter we need to extend this class to create our own customized security.

SecurityConfiguration.java

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private TokenAuthenticationService tokenService;

    @Autowired
    private RedisService redisService;

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(Arrays.asList((AuthenticationProviderImpl) new AuthenticationProviderImpl(redisService)));
    }

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.headers().cacheControl();

        http.csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic().disable()
                .addFilterBefore(new JWTLoginFilter("/login", authenticationManager(), tokenService), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JWTAuthenticationFilter(tokenService), UsernamePasswordAuthenticationFilter.class);

    }

}

This class’s authenticationManager() method has been overridden by our custom authentication AuthenticationProviderImpl.java . There are two filters (JWTLoginFilter, JWTAuthenticationFilter) were added for logging in and another for authenticating the existing JWT token in request. So all we did was disable default basic http security of spring and made all the resources to be available for only authenticated user and rest all are handled by Spring boot security.

 

JWTAuthenticationFilter.java

public class JWTAuthenticationFilter extends GenericFilterBean {

    private TokenAuthenticationService service;

    public JWTAuthenticationFilter(TokenAuthenticationService service) {
        this.service = service;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        Authentication authentication = service.getAuthentication((HttpServletRequest) request);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}

 

JWTLoginFilter.java

public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    private TokenAuthenticationService tokenAuthenticationService;

    public JWTLoginFilter(String url, AuthenticationManager authenticationManager, TokenAuthenticationService service) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authenticationManager);
        tokenAuthenticationService = service;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse hsr1) throws AuthenticationException, IOException, ServletException {
        SessionUser credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), SessionUser.class);
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
        return getAuthenticationManager().authenticate(token);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
            throws IOException, ServletException {
        
        AuthenticationTokenImpl auth = (AuthenticationTokenImpl) authentication;
        tokenAuthenticationService.addAuthentication(response, auth); 
    }
}

We are all good with the security stuffs . In these two filters we are actually calling the TokenAuthenticationService to validate the user’s request token . Now your application is already JWT compatible . Lets create a very simple resource where user can see some restricted stuffs once they are authenticated.

 

UserResource.java

@RestController
@RequestMapping("/user")
public class UserResource {

    @Autowired
    private RedisService service;

    @RequestMapping(method = RequestMethod.GET)
    public String getName(AccessTokenBasedAuth auth, HttpServletResponse response) {
        return auth.getPrincipal().toString();
    }

    @RequestMapping(value = "/processor", method = RequestMethod.GET)
    public Integer getProcessor(AccessTokenBasedAuth auth, HttpServletResponse response) {
        return Runtime.getRuntime().availableProcessors();
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public String logout(AccessTokenBasedAuth auth, HttpServletResponse response) {
        service.setValue(auth.getPrincipal().toString().toLowerCase(), "");
        return "Logout Successfully";
    }

}

This class contains some endpoints which is accessible using specified uri pattern and method type in @RequestMapping. Lets try to do some real tests with this minimal application that we have created.

 

5. Testing

i) Login

Postman testing for logging in

Postman testing for logging in

 

 

ii) Request resource using JWT token

Spring Boot with JWT authentication using Redis

Spring Boot with JWT authentication using Redis

 

 

6. Source code

For the source code it is available in github repository : spring-boot-redis-jwt

 

Please feel free to share your thoughts on this post by commenting below 🙂 Thanks