How Spring Boot uses JWT for authentication and authorization

How Spring Boot uses JWT for authentication and authorization

JSON Web Token (JWT) is an open standard for securely transferring information. JWT is a lightweight authentication and authorization mechanism that securely transmits information between a client and server. In this article, we'll cover how to use JWT for authentication and authorization in a Spring Boot application.

insert image description here

JWT overview

JWT is an open JSON-based standard for securely transferring information between clients and servers. JWT consists of three parts: Header, Payload and Signature.

Header

Header usually consists of two parts: token type and algorithm. For example, the following is an example of a JWT Header:

{
    
    
  "alg": "HS256",
  "typ": "JWT"
}

In the above example, alg represents the algorithm used, and typ represents the type of token.

Payload

Payload contains the information to be transmitted. Payload usually consists of some standard fields and custom fields. For example, here is an example of a JWT Payload:

{
    
    
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

In the above example, sub means the subject, name means the name, and iat means the creation time of the token.

Signature

Signature is used to verify the authenticity and integrity of the token. Signature consists of Header, Payload, and a key, which can be signed using an algorithm. For example, here is an example of a JWT Signature:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

In the above example, secret represents the secret key.

JWT authentication and authorization

In a Spring Boot application, we can use JWT for authentication and authorization. Typically, we need to implement the following steps:

  1. Clients authenticate by username and password.
  2. The server generates a JWT and sends it to the client.
  3. The client stores the JWT locally.
  4. When the client sends a request to the server, the JWT is added to the request header.
  5. The server validates the JWT and authorizes based on the information in the JWT.

Below is a sample Spring Boot application for implementing JWT authentication and authorization.

add dependencies

First, we need to add the following dependencies to the pom.xml file:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

Create entity classes

We need to create a User class to represent user information. For example, here is an example of a User class:

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

    public User() {
    
    }

    public User(String username, String password) {
    
    
        this.username = username;
        this.password = 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;
    }
}

Create an authentication interface

We need to create an authentication interface to handle user authentication requests. For example, here is an example of an AuthController class:

@RestController
@RequestMapping("/auth")
public class AuthController {
    
    
    private final AuthenticationManager authenticationManager;
    private final JwtUtils jwtUtils;

    public AuthController(AuthenticationManager authenticationManager, JwtUtils jwtUtils) {
    
    
        this.authenticationManager = authenticationManager        ;
        this.jwtUtils = jwtUtils;
    }

    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@RequestBody User user) {
    
    
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateJwtToken(authentication);

        return ResponseEntity.ok(new JwtResponse(jwt));
    }
}

In the above example, we have created a class called AuthController with @RestController and @RequestMapping annotations. We have created a method called authenticateUser using @PostMapping and @RequestBody annotations to handle user authentication requests. In this method, we authenticate using the AuthenticationManager object and generate the JWT using the JwtUtils object.

Create JWT tool class

We need to create a JwtUtils class to generate and verify JWT. For example, the following is an example of a JwtUtils class:

@Component
public class JwtUtils {
    
    
    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private int expiration;

    public String generateJwtToken(Authentication authentication) {
    
    
        UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();

        return Jwts.builder()
                .setSubject((userPrincipal.getUsername()))
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + expiration * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public boolean validateJwtToken(String authToken) {
    
    
        try {
    
    
            Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
    
    
            logger.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
    
    
            logger.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
    
    
            logger.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
    
    
            logger.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
    
    
            logger.error("JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }

    public String getUsernameFromJwtToken(String token) {
    
    
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody().getSubject();
    }
}

In the above example, we have created a class named JwtUtils using @Component annotation. We use the @Value annotation to get the JWT key and expiration time. We create a JWT using the Jwts.builder() method, and set the JWT's subject, creation time, and expiration time using the setSubject(), setIssuedAt(), and setExpiration() methods. We sign the JWT using the signWith() method with the HS512 algorithm and generate the JWT using the compact() method. We validate the JWT using the Jwts.parser() method and get the subjects in the JWT using the parseClaimsJws() method. We use various exception handling mechanisms to handle exceptions that may arise during JWT validation.

Create an authorization interceptor

We need to create an authorization interceptor to validate the JWT in the request. For example, the following is an example of a JwtAuthTokenFilter class:

public class JwtAuthTokenFilter extends OncePerRequestFilter {
    
    
    private final JwtUtils jwtUtils;

    public JwtAuthTokenFilter(JwtUtils jwtUtils) {
    
    
        this.jwtUtils = jwtUtils;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        try {
    
    
            String jwt = parseJwt(request);
            if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
    
    
                String username = jwtUtils.getUsernameFromJwtToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
    
    
            logger.error("Cannot set user authentication: {}", e.getMessage());
        }

        filterChain.doFilter(request, response);
    }

    private String parseJwt(HttpServletRequest request) {
    
    
        String headerAuth = request.getHeader("Authorization");

        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
    
    
            return headerAuth.substring(7, headerAuth.length());
        }

        return null;
    }
}

In the above example, we created an interceptor called JwtAuthTokenFilter using the OncePerRequestFilter class. In the doFilterInternal() method, we use the parseJwt() method to get the JWT in the request header and validate the JWT using the validateJwtToken() method in the JwtUtils class. If the JWT validation is successful, we get the username in the JWT using the getUsernameFromJwtToken() method and load the user details using the userDetailsService. We use the UsernamePasswordAuthenticationToken object to create a new authentication object and use the SecurityContextHolder to set it as the current authentication object. Finally, we call the filterChain.doFilter() method to pass the request to the next filter.

Configuring Spring Security¶

We need to add JwtAuthTokenFilter in Spring Security configuration to validate JWT. For example, here is an example of a SecurityConfiguration class:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtUtils jwtUtils;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable().authorizeRequests()
                .antMatchers("/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtAuthTokenFilter(jwtUtils), UsernamePasswordAuthenticationFilter.class);
    }

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    
    
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }
}

In the above example, we have created a class called SecurityConfiguration with @Configuration and @EnableWebSecurity annotations. We injected a UserDetailsServiceImpl object named userDetailsService and a JwtUtils object named jwtUtils using the @Autowired annotation. We configured HTTP security using the configure() method and specified URLs that did not require authentication using the antMatchers() method. We added JwtAuthTokenFilter to validate JWT using addFilterBefore() method. We configured the authentication manager using the configureAuthentication() method and created a password encoder using the passwordEncoder() method. We create an authentication manager using the authenticationManagerBean() method.

Create a response class

Finally, we need to create a JwtResponse class to represent the JWT response. For example, the following is an example of a JwtResponse class:

public class JwtResponse {
    
    
    private final String jwt;

    public JwtResponse(String jwt) {
    
    
        this.jwt = jwt;
    }

    public String getJwt() {
    
    
        return jwt;
    }
}

in conclusion

In this article, we covered how to use JWT for authentication and authorization in Spring Boot applications. We created a User class to represent user information, an AuthController class to handle user authentication requests, a JwtUtils class to generate and verify JWT, a JwtAuthTokenFilter class to verify the JWT in the request, and a SecurityConfiguration class to configure Spring Security, and a JwtResponse class is created to represent the JWT response. We hope this article helped you understand how to use JWT for authentication and authorization in Spring Boot applications.

Guess you like

Origin blog.csdn.net/JasonXu94/article/details/131353326