title: Spring Boot Security + JWT
body: <p>I have a SpringBoot 2.4.2 application that uses JSON Web Tokens (JWT, sometimes pronounced /dʒɒt/, the same as the English word &quot;jot&quot;[1]) is an Internet proposed standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims. The tokens are signed either using a private secret or a public/private key. For example, a server could generate a token that has the claim &quot;logged in as admin&quot; and provide that to a client. The client could then use that token to prove that it is logged in as admin.</p>
  <p>This is my WebSecurityConfig:</p>
  <pre><code>@Configuration
  @EnableWebSecurity
  @EnableGlobalMethodSecurity(prePostEnabled = true)
  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  
      private static final String SALT = &quot;fd23451*(_)nof&quot;;
  
      private final JwtAuthenticationEntryPoint unauthorizedHandler;
      private final JwtTokenUtil jwtTokenUtil;
      private final UserSecurityService userSecurityService;
  
      @Value(&quot;${jwt.header}&quot;)
      private String tokenHeader;
  
  
      public ApiWebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtTokenUtil jwtTokenUtil,
              UserSecurityService userSecurityService) {
          this.unauthorizedHandler = unauthorizedHandler;
          this.jwtTokenUtil = jwtTokenUtil;
          this.userSecurityService = userSecurityService;
      }
  
      @Autowired
      public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
          auth
                  .userDetailsService(userSecurityService)
                  .passwordEncoder(passwordEncoder());
      }
  
      @Bean
      public BCryptPasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
      }
  
      @Bean
      @Override
      public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
      }
  
      @Override
      protected void configure(HttpSecurity httpSecurity) throws Exception {
  
          httpSecurity
                  // we don't need CSRF because our token is invulnerable
                  .csrf().disable()
  
                  .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
  
                  // don't create session
                  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                  .authorizeRequests()
                  // Un-secure H2 Database
                  .antMatchers(&quot;/h2-console/**/**&quot;).permitAll()
                  .antMatchers(&quot;/api/v1/users&quot;).permitAll()
                  .anyRequest().authenticated();
  
          // Custom JWT based security filter
          JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
          httpSecurity
                  .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  
          // disable page caching
          httpSecurity
                  .headers()
                  .frameOptions()
                  .sameOrigin()  // required to set for H2 else H2 Console will be blank.
                  .cacheControl();
      }
  
      @Override
      public void configure(WebSecurity web) {
  
          // AuthenticationTokenFilter will ignore the below paths
          web
                  .ignoring()
                  .antMatchers(
                          HttpMethod.POST,
                          &quot;/api/v1/users&quot;
                  );
  
      }
  
  }
  </code></pre>
  <p>and this is my Filter:</p>
  <pre><code>@Provider
  @Slf4j
  public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
  
      private UserDetailsService userDetailsService;
      private JwtTokenUtil jwtTokenUtil;
      private String tokenHeader;
  
      public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, String tokenHeader) {
          this.userDetailsService = userDetailsService;
          this.jwtTokenUtil = jwtTokenUtil;
          this.tokenHeader = tokenHeader;
      }
  
  
      @Override
      protected boolean shouldNotFilter(HttpServletRequest request) {
          return new AntPathMatcher().match(&quot;/api/v1/users&quot;, request.getServletPath());
      }
  
  
      @Override
      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException,
              IOException {
  
          log.info(&quot;processing authentication for '{}'&quot;, request.getRequestURL());
  
          final String requestHeader = request.getHeader(this.tokenHeader);
  
          String username = null;
          String authToken = null;
  
          if (requestHeader != null &amp;&amp; requestHeader.startsWith(&quot;Bearer &quot;)) {
              authToken = requestHeader.substring(7);
              try {
                  username = jwtTokenUtil.getUsernameFromToken(authToken);
              } catch (IllegalArgumentException e) {
                  logger.info(&quot;an error occured during getting username from token&quot;, e);
              } catch (ExpiredJwtException e) {
                  logger.info(&quot;the token is expired and not valid anymore&quot;, e);
              }
          } else {
              logger.info(&quot;couldn't find bearer string, will ignore the header&quot;);
          }
  
          log.info(&quot;checking authentication for user '{}'&quot;, username);
  
          if (username != null &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == null) {
              logger.info(&quot;security context was null, so authorizating user&quot;);
  
              // It is not compelling necessary to load the use details from the database. You could also store the information
              // in the token and read it from it. It's up to you ;)
              UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  
              // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
              // the database compellingly. Again it's up to you ;)
              if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                  UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                  authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                  log.info(&quot;authorizated user '{}', setting security context&quot;, username);
                  SecurityContextHolder.getContext().setAuthentication(authentication);
              }
          }
          chain.doFilter(request, response);
      }
  }
  </code></pre>
  <p>and</p>
  <pre><code>@Component
  @Slf4j
  public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
  
      private static final long serialVersionUID = -8970718410437077606L;
  
      @Override
      public void commence(HttpServletRequest request,
              HttpServletResponse response,
              AuthenticationException authException) throws IOException {
  
          log.info(&quot;user tries to access a secured REST resource without supplying any credentials&quot;);
  
          // This is invoked when user tries to access a secured REST resource without supplying any credentials
          // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
          response.sendError(HttpServletResponse.SC_UNAUTHORIZED, &quot;Unauthorized&quot;);
      }
  }
  </code></pre>
  <p>This is the console when I start the app:</p>
  <pre><code>18:02:51.974 [restartedMain] DEBUG com.agrumh.Application - Running with Spring Boot v2.4.2, Spring v5.3.3
  18:02:51.974 [restartedMain] INFO  com.agrumh.Application - No active profile set, falling back to default profiles: default
  18:02:57.383 [restartedMain] INFO  o.s.s.web.DefaultSecurityFilterChain - Will secure Ant [pattern='/api/v1/users', POST] with []
  18:02:57.414 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [permitAll] for Ant [pattern='/h2-console/**/**']
  18:02:57.415 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [permitAll] for Ant [pattern='/api/v1/users']
  18:02:57.416 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [authenticated] for any request
  18:02:57.422 [restartedMain] INFO  o.s.s.web.DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@24c68fed, org.springframework.security.web.context.SecurityContextPersistenceFilter@1537eb0a, org.springframework.security.web.header.HeaderWriterFilter@95de45c, org.springframework.security.web.authentication.logout.LogoutFilter@733cf550, com.dispacks.config.JwtAuthorizationTokenFilter@538a96c8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@8d585b2, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@784cf061, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@64915f19, org.springframework.security.web.session.SessionManagementFilter@21f180d0, org.springframework.security.web.access.ExceptionTranslationFilter@2b153a28, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4942d157]
  18:02:58.619 [restartedMain] INFO  com.dispacks.DispacksApplication - Started DispacksApplication in 6.974 seconds (JVM running for 7.697)
  18:04:03.685 [http-nio-1133-exec-1] DEBUG o.s.security.web.FilterChainProxy - Securing POST /error
  18:04:03.687 [http-nio-1133-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext
  18:04:03.689 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
  18:04:03.694 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [POST /error] with attributes [authenticated]
  18:04:03.698 [http-nio-1133-exec-1] INFO  c.d.s.JwtAuthenticationEntryPoint - user tries to access a secured REST resource without supplying any credentials
  18:04:03.699 [http-nio-1133-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Cleared SecurityContextHolder to complete request
  </code></pre>
  <p>But when I access with Postman I have this error:</p>
  <pre><code>22:58:33.562 [http-nio-1133-exec-2] WARN  o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain' not supported]
  22:58:33.579 [http-nio-1133-exec-2] INFO  c.d.s.JwtAuthenticationEntryPoint - user tries to access a secured REST resource without supplying any credentials
  </code></pre>