Spring Security and JWT
JSON Web Tokens (JWT) provide a compact and secure way to transmit information between parties. Spring Security offers robust support for integrating JWT authentication. This guide covers key concepts and steps for setting up JWT authentication in your Spring Boot application, including adding dependencies, creating JWT tokens, configuring JWT security, and securing endpoints.
Key Concepts of Spring Security and JWT
- JWT (JSON Web Token): A compact, URL-safe token that represents claims between two parties.
- JWT Structure: Consists of three parts: Header, Payload, and Signature.
- JWT Authentication: A method to authenticate users by validating JWT tokens.
- Security Configuration: Configuring Spring Security to use JWT for authentication.
Adding Dependencies
Include the Spring Security and JWT dependencies in your pom.xml
file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Creating JWT Tokens
Create JWT tokens by defining a utility class to generate and validate tokens:
Example: JwtUtil.java
// JwtUtil.java
package com.example.myapp.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private String SECRET_KEY = "secret";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public T extractClaim(String token, Function claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(String username) {
Map claims = new HashMap<>();
return createToken(claims, username);
}
private String createToken(Map claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
}
Configuring JWT Security
Configure JWT security by creating filters and security configuration classes:
Example: JwtRequestFilter.java
// JwtRequestFilter.java
package com.example.myapp.filter;
import com.example.myapp.service.MyUserDetailsService;
import com.example.myapp.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Example: SecurityConfiguration.java
// SecurityConfiguration.java
package com.example.myapp.config;
import com.example.myapp.filter.JwtRequestFilter;
import com.example.myapp.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate", "/register").permitAll()
.anyRequest().authenticated()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Securing Endpoints
Protect specific endpoints by specifying access rules:
Example: SecurityConfiguration.java
// SecurityConfiguration.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate", "/register").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
Generating JWT Tokens
Create a controller to authenticate users and generate JWT tokens:
Example: AuthController.java
// AuthController.java
package com.example.myapp.controller;
import com.example.myapp.service.MyUserDetailsService;
import com.example.myapp.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/authenticate")
public String createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails.getUsername());
return jwt;
}
}
Example: AuthenticationRequest.java
// AuthenticationRequest.java
package com.example.myapp.model;
public class AuthenticationRequest {
private String username;
private String password;
// getters and setters
}
Key Points
- JWT (JSON Web Token): A compact, URL-safe token that represents claims between two parties.
- JWT Structure: Consists of three parts: Header, Payload, and Signature.
- JWT Authentication: A method to authenticate users by validating JWT tokens.
- Security Configuration: Configuring Spring Security to use JWT for authentication.
- Include the Spring Security and JWT dependencies in your
pom.xml
file. - Create JWT tokens by defining a utility class to generate and validate tokens.
- Configure JWT security by creating filters and security configuration classes.
- Protect specific endpoints by specifying access rules.
- Create a controller to authenticate users and generate JWT tokens.
Conclusion
Integrating JWT with Spring Security allows you to leverage the power of JWT for secure and stateless authentication in your Spring Boot applications. By understanding and configuring JWT tokens, security settings, and authentication flows, you can ensure secure access to your application's resources. Happy coding!