REST API Security with Spring Security JWT Token Signing
Securing REST APIs is important in today’s web development, especially with microservices becoming more common. A popular way to do this is with JSON Web Tokens (JWT). Spring Security helps with JWT-based authentication and authorization in Spring applications. In this article, we’ll see how to create a Spring Security key for signing JWT tokens and use it in a Spring Boot app to secure REST APIs.
1. Set up a Spring Boot Application
Let’s begin by creating a new Spring Boot application by either using Spring Initializr (https://start.spring.io/) or your preferred IDE to create a new project with the necessary dependencies:
- Spring Web
- Spring Security

2. Add JSON Web Token Dependencies
In the pom.xml file of the project (if using Maven), add the following dependencies for JWT token handling:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
3. Create JWT Utility Class
Next, create a utility class for handling JWT operations. This class will be responsible for generating JWT tokens and verifying them. Below is a simple implementation of the JWT utility class:
@Component
public class JwtUtil {
@Value("${jcg.jwt.secret}")
private String jwtSecret;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("Authorities", userDetails.getAuthorities());
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private SecretKey getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
return Keys.hmacShaKeyFor(keyBytes);
}
}
The code block above represents a class named JwtUtil which is responsible for handling JWT (JSON Web Token) operations within our application. Here is a break down its functionalities:
- The class injects the JWT secret key from the
application.propertiesfile using the@Valueannotation. - Token Generation: The
generateTokenmethod creates a JWT token based on the providedUserDetails. It sets the subject (username), issue date, expiration date, and signs the token using the HMAC SHA-256 algorithm with the secret key. extractUsernameMethod: This method takes a JWT token as input and returns the username extracted from the token’s subject claim. It delegates the claim extraction process to theextractClaimmethod, passing in the token and a function reference (Claims::getSubject) to extract the subject claim.extractClaimMethod: This generic method takes a JWT token and aFunctionthat resolves a specific claim from the token’sClaimsobject. It first extracts all claims from the token by invoking theextractAllClaimsmethod. Then, it applies the providedclaimsResolverfunction to theClaimsobject to extract the desired claim.extractAllClaimsMethod: This method is responsible for parsing the JWT token, verifying its signature using the provided signing key, and extracting all claims from the token’s body. It uses theJwts.parser()method to create a parser instance, sets the signing key withsetSigningKey(getSignInKey()), and then parses the token withparseClaimsJws(token).- Secret Key Retrieval: The
getSignInKeymethod retrieves the secret key for signing and verifying JWT tokens. It decodes the base64-encoded secret key obtained from theapplication.propertiesfile and converts it into aSecretKeyobject.
3.1 Generate and Set JWT Secret Key
Producing a strong secret key is essential to ensure the security of JWT tokens. This entails generating an HMAC hash string consisting of 256 bits and configuring it as the JWT secret within the application.properties file. The online tool generator located at devglan.com/online-tools can generate an HMAC hash string of 256 bits.
3.1.1 Set JWT Secret in application.properties
Once the secret key is generated, it needs to be set in the application.properties file of the Spring Boot application:
jcg.jwt.secret=YOUR_GENERATED_SECRET_KEY
Replace YOUR_GENERATED_SECRET_KEY with the generated HMAC hash string obtained earlier.
3.2 Implementing Authentication Token Filter
Next, let’s implement an Authentication Token Filter which is crucial for securing REST endpoints with JWT tokens. Below is an example of an authentication token filter:
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
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)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
This filter intercepts incoming requests, extracts JWT tokens from the request header, validates them, and sets up authentication in the Spring Security context if the token is valid.
4. Configure Spring Security
Next, configure Spring Security to use JWT for authentication. Create a SecurityConfig class and configure it as follows:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final String ADMIN = "ADMIN";
private static final String USER = "USER";
@Bean
public JwtRequestFilter jwtRequestFilter() {
return new JwtRequestFilter();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() throws Exception {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(req -> req
.requestMatchers("/admin/**").hasRole(ADMIN)
.requestMatchers("/user/**").hasAnyRole(USER, ADMIN)
.requestMatchers("/authenticate")
.permitAll()
.anyRequest()
.authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User
.withUsername("thomas")
.password(encoder().encode("paine"))
.roles(ADMIN).build());
manager.createUser(User
.withUsername("bill")
.password(encoder().encode("withers"))
.roles(USER).build());
return manager;
}
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source
= new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
This code block configures security settings for the application using Spring Security. Let’s break down its functionalities:
- The
jwtRequestFiltermethod defines a bean for theJwtRequestFilterclass. This filter intercepts incoming requests, extracts JWT tokens, and authenticates them. - DaoAuthenticationProvider Bean: The
authenticationProvidermethod defines a bean for theDaoAuthenticationProviderclass. This provider authenticates users based on the provided user details service and password encoder. - SecurityFilterChain Bean: The
securityFilterChainmethod sets up authorization rules based on request matchers and roles.- Authorization Rules: The
.authorizeHttpRequests()method specifies authorization rules for different types of requests:- Requests to
/admin/**endpoints require theADMINrole. - Requests to
/user/**endpoints require either theUSERorADMINrole. - Requests to
/authenticateendpoint are permitted without authentication (permit all). - All other requests (
anyRequest()) require authentication.
- Requests to
- Session Management: Session management is configured to be stateless (
sessionCreationPolicy(STATELESS)), meaning no server-side session will be created or used for storing user authentication state.
- Authorization Rules: The
- AuthenticationManager Bean: The
authenticationManagermethod defines a bean for theAuthenticationManagerinterface. It retrieves the authentication manager from the authentication configuration. - UserDetailsService Bean: The
userDetailsServicemethod defines an in-memory user store usingInMemoryUserDetailsManager. We create two users with different roles: anADMINuser with username “thomas” and aUSERuser with username “bill“.
4.1 Custom UserDetails Implementation
Spring Security relies on the UserDetails interface for both authentication and authorization purposes. Below is an implementation of the User class which implements the UserDetails interface for authentication and authorization:
public class User implements UserDetails {
private int id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public User() {
}
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.password = password;
this.username = username;
this.authorities = authorities;
}
public User(String username, Collection<String> authorities) {
this.username = username;
this.authorities = authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
// UserDetails interface methods
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
}
The User class implements the UserDetails interface and represents a user in the application. It includes fields for username, password, and authorities. The UserDetails interface is a core interface in Spring Security used for user authentication and authorization. It represents a principal (user) in the system and provides methods for accessing user details and authorities.
getAuthorities() method returns the authorities (roles) granted to the user. getPassword() and getUsername() methods return the password and username of the user, respectively. isAccountNonExpired() isAccountNonLocked(), isCredentialsNonExpired(), isEnabled() methods return true if the user account is not expired, not locked, credentials are not expired, and the user is enabled, respectively.
5. Integration with Spring Boot
Finally, Let’s integrate the JWT utility and Spring Security configurations with our Spring Boot application. Here’s a basic example of a REST controller for authentication:
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/authenticate")
public ResponseEntity createAuthenticationToken(@RequestBody AuthRequest authRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthResponse(jwt));
}
@GetMapping("/auth/details")
public UserDetails getDetails(){
var detail = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return detail;
}
}
This block of code defines an AuthController class responsible for handling authentication-related HTTP requests in the application.
- The controller class autowires the dependencies (
AuthenticationManager,JwtUtil, andUserDetailsService) required for authentication and token generation. - createAuthenticationToken Method: This method handles POST requests to
/authenticateendpoint. It attempts to authenticate the user by passing the provided credentials to theAuthenticationManager. If authentication is successful, it generates a JWT token usingJwtUtiland returns it in the response body. - getDetails Method: This method handles GET requests to
/auth/detailsendpoint. It retrieves the authenticated user’s details (such as username, authorities) from theSecurityContextHolder.
5.1 Securing REST Endpoints
@RestController
public class SimpleController {
@RolesAllowed("ADMIN")
@GetMapping("/admin")
public ResponseEntity<String> testAdmin() {
return ResponseEntity.ok("This is the Admin role");
}
@RolesAllowed("USER")
@GetMapping("/user")
public ResponseEntity<String> testUser() {
return ResponseEntity.ok("This is the User role");
}
}
This SimpleController class defines endpoints that are accessible only to users with specific roles. It enforces role-based access control (RBAC), ensuring that certain operations can only be performed by users with the appropriate roles.
- testAdmin Method: This method handles GET requests to the
/adminendpoint. It is annotated with@RolesAllowed("ADMIN"), which specifies that only users with the role “ADMIN” are allowed to access this endpoint. - testUser Method: This method handles GET requests to the
/userendpoint. Similar to thetestAdminmethod, it is annotated with@RolesAllowed("USER"), indicating that only users with the role “USER” can access this endpoint.
To verify the functionality of the application, we can utilize POSTMAN to send a request to http://localhost:8080/authenticate and acquire a JWT token.

Next, proceed by setting the request header with the JWT key and verify if http://localhost:8080/auth/details functions correctly.

Now, let’s verify if we can access the http://localhost:8080/user endpoint:
If a user with the USER role attempts to access the /admin endpoint, Spring Security will deny the request, and the user will receive an HTTP status code indicating access forbidden (403).
6. Conclusion
In this article, we’ve learned how to generate a key to sign JWT tokens and include it in a Spring Boot application for security. We also saw how to secure specific endpoints based on user roles. By using Spring Security to sign a JWT token, we greatly improve the security of our application.
7. Download the Source Code
This was an article on how to create a spring security key to sign JWT Token to Secure REST APIs.
You can download the full source code of this example here: Create Spring Security Key to Sign JWT Token to Secure REST APIs






