title: Approaches for logout functionality in Spring Boot app (Spring Security + JWT)
body: <p>I create Spring Boot + Spring Security app with JWT authentication and it works something like this:</p>
  
  <ul>
  <li>client sends username, password to the login endpoint;</li>
  <li>server checks if the provided credentials are valid and return a token;</li>
  <li>client sends the token with every future request.</li>
  </ul>
  
  <p>It's based on <a href="https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/" rel="nofollow noreferrer">this example</a> and approach for logout functionality which author suggest - just remove JWT token on client-side. I don't think that is good idea, and I'd like to implement it on back-end. </p>
  
  <p>As I understand I need to remove JWT token of user which call logout REST method on server-side, after that when this user will call other REST methods he should get responses with 403 error. I investigated ways how can I add this feature and didn't find any appropriate solution. </p>
  
  <p>Here are details of my implementation (there may be some differences from the example from the link above):</p>
  
  <pre><code>@EnableWebSecurity
  public class WebSecurity extends WebSecurityConfigurerAdapter {
      private UserDetailsService userDetailsService;
      private BCryptPasswordEncoder bCryptPasswordEncoder;
  
      public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
          this.userDetailsService = userDetailsService;
          this.bCryptPasswordEncoder = bCryptPasswordEncoder;
      }
  
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.cors().and().csrf().disable().authorizeRequests()
                  .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
                  .antMatchers(HttpMethod.POST, LOGIN_URL).permitAll()
                  .antMatchers(HttpMethod.GET, HEALTH_URL).permitAll()
                  .anyRequest().authenticated()
                  .and()
                  .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                  .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                  // this disables session creation on Spring Security
                  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
      }
  
      @Override
      public void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
      }
  
      @Bean
      CorsConfigurationSource corsConfigurationSource() {
          final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
          source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
          return source;
      }
  }
  
  public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
  
      public JWTAuthorizationFilter(AuthenticationManager authManager) {
          super(authManager);
      }
  
      @Override
      protected void doFilterInternal(HttpServletRequest req,
                                      HttpServletResponse res,
                                      FilterChain chain) throws IOException, ServletException {
          String header = req.getHeader(HEADER_STRING);
          if (header == null || !header.startsWith(TOKEN_PREFIX)) {
              chain.doFilter(req, res);
              return;
          }
          UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
          SecurityContextHolder.getContext().setAuthentication(authentication);
          chain.doFilter(req, res);
      }
  
      private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
          String token = request.getHeader(HEADER_STRING);
          if (token != null) {
              String user = Jwts.parser()
                      .setSigningKey(SECRET.getBytes())
                      .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                      .getBody()
                      .getSubject();
              if (user != null) {
                  return new UsernamePasswordAuthenticationToken(user, null, new ArrayList&lt;&gt;());
              }
              return null;
          }
          return null;
      }
  }
  
  
  public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
      private AuthenticationManager authenticationManager;
  
      public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
          this.authenticationManager = authenticationManager;
          setFilterProcessesUrl(LOGIN_URL);
      }
  
      @Override
      public Authentication attemptAuthentication(HttpServletRequest req,
                                                  HttpServletResponse res) throws AuthenticationException {
          try {
              User creds = new ObjectMapper().readValue(req.getInputStream(), User.class);
              return authenticationManager.authenticate(
                      new UsernamePasswordAuthenticationToken(
                              creds.getUsername(),
                              creds.getPassword(),
                              new ArrayList&lt;&gt;())
              );
          } catch (IOException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      protected void successfulAuthentication(HttpServletRequest req,
                                              HttpServletResponse res,
                                              FilterChain chain,
                                              Authentication auth) throws IOException, ServletException {
          String token = Jwts.builder()
                  .setSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
                  .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                  .signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
                  .compact();
          res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
      }
  }
  
  public class SecurityConstants {
      public static final String SECRET = "SecretKeyToGenJWTs";
      public static final long EXPIRATION_TIME = 864_000_000; // 10 days
      public static final String TOKEN_PREFIX = "Bearer ";
      public static final String HEADER_STRING = "Authorization";
  }
  </code></pre>