Securing your backend using JWT

In the previous section, we covered how to use basic authentication with the RESTful web service. This method cannot be used when we develop our own frontend with React, so we are going to use JSON Web Token (JWT) authentication instead. JWT is a compact way to implement authentication in modern web applications. JWT is really small in size and can therefore be sent in the URL, in the POST parameter, or inside the header. It also contains all the necessary information pertaining to the user.

The JSON web token contains three different parts, separated by dots:

  • The first part is the header that defines the type of the token and the hashing algorithm.
  • The second part is the payload that, typically, in the case of authentication, contains information pertaining to the user.
  • The third part is the signature that is used to verify that the token hasn't been changed along the way. The following is an example of a JWT token:
eyJhbGciOiJIUzI1NiJ9.
eyJzdWIiOiJKb2UifD.
ipevRNuRP6HflG8cFKnmUPtypruRC4fc1DWtoLL62SY

The following diagram shows the main idea of the JWT authentication process:

After successful authentication, the requests sent by the user should always contain the JWT token that was received in the authentication.

We will use the Java JWT library (https://github.com/jwtk/jjwt), which is the JWT library for Java and Android. Therefore, we have to add the following dependency to the pom.xml file. The JWT library is used for creating and parsing JWT tokens:

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
If you are using Java version 9 or greater, add the following dependency to the pom.xml file:
<dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
</dependency>

The following steps demonstrate how to enable JWT authentication in our backend:

  1.  Create a new class called AuthenticationService in the service package. At the beginning of the class, we will define a few constants—EXPIRATIONTIME defines the expiration time of the token in milliseconds, while SIGNINGKEY is an algorithm-specific signing key that's used to digitally sign the JWT. You should use a base64-encoded string to do this. PREFIX defines the prefix of the token, and the Bearer schema is typically used. The addToken method creates the token and adds it to the request's Authorization header. The signing key is encoded using the SHA-512 algorithm. The method also adds Access-Control-Expose-Headers to the header with the Authorization value. This is needed because we are unable to access the Authorization header through a JavaScript frontend by default. The getAuthentication method gets the token from the response Authorization header using the parser() method provided by the jjwt library. The whole AuthenticationService source code can be seen here:
package com.packt.cardatabase.service;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

import static java.util.Collections.emptyList;

public class AuthenticationService {
static final long EXPIRATIONTIME = 864_000_00; // 1 day in milliseconds
static final String SIGNINGKEY = "SecretKey";
static final String PREFIX = "Bearer";

// Add token to Authorization header
static public void addToken(HttpServletResponse res, String username) {
String JwtToken = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis()
+ EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS512, SIGNINGKEY)
.compact();
res.addHeader("Authorization", PREFIX + " " + JwtToken);
res.addHeader("Access-Control-Expose-Headers", "Authorization");
}

// Get token from Authorization header
static public Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (token != null) {
String user = Jwts.parser()
.setSigningKey(SIGNINGKEY)
.parseClaimsJws(token.replace(PREFIX, ""))
.getBody()
.getSubject();

if (user != null)
return new UsernamePasswordAuthenticationToken(user, null,
emptyList());
}
return null;
}
}
  1. Next, we will add a new simple POJO class to keep credentials for authentication. Create a new class called AccountCredentials in the domain package. The class has two fields—username and password. The following is the source code of the class. This class doesn't have the @Entity annotation because we don't have to save credentials to the database:
package com.packt.cardatabase.domain;

public class AccountCredentials {
private String username;
private String password;

public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
  1. We will use filter classes for login and authentication. Create a new class called LoginFilter in the root package that handles POST requests to the /login endpoint. The LoginFilter class extends Spring Security's AbstractAuthenticationProcessingFilter interface, which requires that you set the authenticationManager property. Authentication is performed by the attemptAuthentication method. If authentication is successful, the succesfulAuthentication method is executed. This method will then call the addToken method in our service class, and the token will be added to the Authorization header:
package com.packt.cardatabase;

import java.io.IOException;
import java.util.Collections;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.packt.cardatabase.domain.AccountCredentials;
import com.packt.cardatabase.service.AuthenticationService;

public class LoginFilter extends AbstractAuthenticationProcessingFilter {

public LoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}

@Override
public Authentication attemptAuthentication(
HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException, IOException, ServletException {
AccountCredentials creds = new ObjectMapper()
.readValue(req.getInputStream(), AccountCredentials.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
Collections.emptyList()
)
);
}

@Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException, ServletException {
AuthenticationService.addToken(res, auth.getName());
}
}
  1. Create a new class called AuthenticationFilter in the root package. The class extends GenericFilterBean, which is a generic superclass for any type of filter. This class will handle authentication in all other endpoints, except /loginAuthenticationFilter uses the getAuthentication method from our service class to get a token from the request Authorization header:
package com.packt.cardatabase;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import com.packt.cardatabase.service.AuthenticationService;

public class AuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest)request);

SecurityContextHolder.getContext().
setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
  1. Finally, we have to make changes to our SecurityConfig class's configure method. There, we stipulate that the POST method request to the /login endpoint is allowed without authentication and that requests to all other endpoints require authentication. We also define the filters to be used in the /login and other endpoints by using the addFilterBefore method:
  //SecurityConfig.java  
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and().authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// Filter for the api/login requests
.addFilterBefore(new LoginFilter("/login",
authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// Filter for other requests to check JWT in header
.addFilterBefore(new AuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}

  1. We will also add a CORS (short for Cross-Origin Resource Sharing) filter in our security configuration class. This is needed for the frontend, which is sending requests from the other origin. The CORS filter intercepts requests, and if these are identified as cross-origin, it adds proper headers to the request. For that, we will use Spring Security's CorsConfigurationSource interface. In this example, we will allow all HTTP methods and headers (using "*"). You can define the list of permissible origins, methods, and headers here if you require a more finely graded definition. Add the following source code to your SecurityConfig class to enable the CORS filter:
  // SecurityConfig.java  
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowCredentials(true);
config.applyPermitDefaultValues();

source.registerCorsConfiguration("/**", config);
return source;
}

Now, after we run the application, we can call the /login endpoint with the POST method and, in the case of a successful login, we will receive a JWT token in the Authorization header:

Following a successful login, we can call the other RESTful service endpoints by sending the JWT token that was received from the login in the Authorization header. Refer to the example in the following screenshot:

Now, all the functionalities that are required have been implemented to our backend. Next, we will continue with backend unit testing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.188.11