Securing REST APIs with Spring Security
Securing REST APIs is crucial to protect your application from unauthorized access. This guide covers key concepts and steps for securing REST APIs in your Spring Boot application, including adding dependencies, configuring security, and protecting endpoints.
Key Concepts of Securing REST APIs
- REST API Security: Implementing measures to protect your API from unauthorized access.
- JWT (JSON Web Token): A compact, URL-safe token used for secure data transmission.
- Security Configuration: Configuring Spring Security to secure REST APIs.
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>
Configuring JWT Security
Configure JWT security by creating utility classes for generating and validating JWT 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));
}
}
Creating JWT Filters
Create filters to intercept requests and validate JWT tokens:
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);
}
}
Configuring Security
Configure security by extending WebSecurityConfigurerAdapter
and overriding the configure(HttpSecurity http)
method:
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.core.userdetails.UserDetailsService;
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);
}
}
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
}
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);
}
Testing JWT Authentication
Use tools like Postman or curl to test your secured REST APIs:
Example: Testing with curl
$ curl -X POST -H "Content-Type: application/json" -d '{"username":"user", "password":"password"}' http://localhost:8080/authenticate
$ curl -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:8080/secured-endpoint
Key Points
- REST API Security: Implementing measures to protect your API from unauthorized access.
- JWT (JSON Web Token): A compact, URL-safe token used for secure data transmission.
- Security Configuration: Configuring Spring Security to secure REST APIs.
- Include the Spring Security and JWT dependencies in your
pom.xml
file. - Configure JWT security by creating utility classes for generating and validating JWT tokens.
- Create filters to intercept requests and validate JWT tokens.
- Configure security by extending
WebSecurityConfigurerAdapter
and overriding theconfigure(HttpSecurity http)
method. - Create a controller to authenticate users and generate JWT tokens.
- Protect specific endpoints by specifying access rules.
- Use tools like Postman or curl to test your secured REST APIs.
Conclusion
Securing REST APIs with Spring Security and JWT ensures that your application is protected from unauthorized access. By understanding and implementing JWT security, security configuration, and authentication flows, you can enhance the security of your Spring Boot application. Happy coding!