Start with the security configuration:
@Configuration@EnableWebSecuritypublic class SecurityConfigurer {
private final JwtRequestFilter jwtRequestFilter;
// the below is a hardcoded user @Bean public UserDetailsService userDetailsService() { return username -> new User("foo", new BCryptPasswordEncoder().encode("foo"), new ArrayList<>()); }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/login").permitAll() .anyRequest().authenticated() .and() .sessionManagement() // we need the session stateless since we're using JWT, but a filter must // intercept requests and validate the token before that — hence // addFilterBefore below, running the JwtRequestFilter ahead of the // default UsernamePasswordAuthenticationFilter .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class) ) .httpBasic(Customizer.withDefaults()); return http.build(); }}Customize the user details service to load a user by username:
@Servicepublic class MyUserDetailsService implements UserDetailsService{ private final PasswordEncoder passwordEncoder;
public MyUserDetailsService(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; }
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User("foo", passwordEncoder.encode("foo"), new ArrayList<>()); }}Add the jjwt dependency (a Java implementation similar to jwt.io) and jaxb-api:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version></dependency><dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version></dependency>A JwtUtil class abstracts all the JWT-related work — creating a JWT from an Authentication,
extracting the username, checking expiry, and validating tokens:
import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Service;
import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.function.Function;
@Servicepublic 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> 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(SECRET_KEY).parseClaimsJws(token).getBody(); }
private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); }
public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userDetails.getUsername()); }
private String createToken(Map<String, Object> 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, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); }}DTOs for the login request/response:
public class AuthenticationRequest {
private String username; private String password;
public AuthenticationRequest() { }
public AuthenticationRequest(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; }}public class AuthenticationResponse { private final String jwt;
public AuthenticationResponse(String jwt) { this.jwt = jwt; }}A login controller taking username/password as the request body:
private final AuthenticationManager authenticationManager; // authenticates the request bodyprivate final MyUserDetailsService userDetailsService; // loads the user by username
@PostMapping("/login")public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()) ); } catch (BadCredentialsException e) { throw new Exception("Incorrect username or password", e); } // if authentication succeeds, generate a jwt token final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); final String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));}A POST to /login with valid credentials returns a JWT you can use to authenticate future
requests. But first you need a filter that intercepts requests and validates the token — treating
anything with a Bearer prefix as a JWT:
@Componentpublic class JwtRequestFilter extends OncePerRequestFilter {
private final MyUserDetailsService userDetailsService; private JwtUtil jwtUtil;
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // runs once per request, checking the Authorization header 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); } } chain.doFilter(request, response); }}