title: Microservices Spring Cloud Gateway + Spring Security LDAP as SSO + JWT - Token lost between request/response
body: <p>I am developing a microservice ecosystem using spring-boot. The microservices which are in place at the moment :</p>
  <ul>
  <li>Spring Cloud Gateway - Zuul (responsible also for authorization requests downstream for microservices - extracting tokens from requests and validates whether the user has the right role to perform requests),</li>
  <li>SSO using spring security LDAP ( responsible for authenticate user and generate JWT tokens) , SSO has also just a login page using thymeleaf</li>
  <li>Web interface using Thymeleaf without login page ( not sure if I should use here spring security, at the moment)</li>
  <li>Another microservice which provides data to web ui based on request from the browser</li>
  <li>Discovery services using Eureka</li>
  </ul>
  <p>The idea is filtering all the requests on the gateway for validating and forward the requests. If the user is not authenticated or token is experied then forward the user to SSO for login.
  The firewall will expose only the port on Gateway side then others one will be theirs ports blocked using firewall rules.</p>
  <p>Now i am blocked without knowing where to go or if I should move the SSO together with the gateway ( conceptually wrong but it might be a workaround if i do not find any solution)</p>
  <p>Following the issue : The user hits the gateway (ex. http://localhost:7070/web) then the gateway forward the user to (ex. http://localhost:8080/sso/login), after the credentials have been validated , the SSO creates the JWT tokens and add it to the Header of response.
  Afterwards the SSO redirect the request back to the gateway (ex. http://localhost:7070/web).</p>
  <p><strong>Until here, everything works fine but when the request reaches the gateway there is no 'Authorization' header on request which means NO JWT token.</strong></p>
  <p>So the gateway should extract the token, check the credentials and forward the request to the Web interface (ex. http://localhost:9090)</p>
  <p>I am aware that using Handler on SSO to redirect request won't work at all due to 'Redirect' from spring will remove the token from the header before redirect.
  But I do not know whether there is another way to set again the JWT on the header after Spring has removed it from the request or not.</p>
  <p>Is there any conceptually issue on the architecture side? How can I forward the JWT to the gateway for being checked?</p>
  <p>SSO</p>
  <pre><code>@EnableWebSecurity
  public class SecurityCredentialsConfig extends WebSecurityConfigurerAdapter {
  
      @Value(&quot;${ldap.url}&quot;)
      private String ldapUrl;
  
      @Override
      protected void configure(HttpSecurity http) throws Exception {
  
          http
              .csrf().disable()
  
              // Stateless session; session won't be used to store user's state.
              .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  
              .and()
  
              .formLogin()
              .loginPage(&quot;/login&quot;)
              // Add a handler to add token in the response header and forward the response
              .successHandler(jwtAuthenticationSuccessHandler())
              .failureUrl(&quot;/login?error&quot;)
              .permitAll()
  
              .and()
  
              // handle an authorized attempts
              .exceptionHandling()
              .accessDeniedPage(&quot;/login?error&quot;)
  
              .and()
  
              .authorizeRequests()
              .antMatchers( &quot;/dist/**&quot;, &quot;/plugins/**&quot;).permitAll()
              .anyRequest().authenticated();
  
      }
  
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth
              .ldapAuthentication()
              .userDnPatterns(&quot;uid={0},ou=people&quot;)
              .groupSearchBase(&quot;ou=groups&quot;)
              .userSearchFilter(&quot;uid={0}&quot;)
              .groupSearchBase(&quot;ou=groups&quot;)
              .groupSearchFilter(&quot;uniqueMember={0}&quot;)
              .contextSource()
              .url(ldapUrl);
      }
  
  
  
      @Bean
      public AuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
          return new JwtAuthenticationSuccessHandler();
      }
  }
  
      public class JwtAuthenticationSuccessHandler extends  SimpleUrlAuthenticationSuccessHandler  {
  
      @Autowired
      private JwtConfig jwtConfig;
      @Autowired
      private JwtTokenService jwtTokenService;
  
      @Override
      public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
  
          String token = jwtTokenService.expiring(ImmutableMap.of(
                                                              &quot;email&quot;, auth.getName(),
                                                              &quot;authorities&quot;, auth.getAuthorities()
                                                                      .stream()
                                                                      .map(GrantedAuthority::getAuthority)
                                                                      .map(Object::toString)
                                                                      .collect(Collectors.joining(&quot;,&quot;))));
  
          response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
  
          DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute(&quot;SPRING_SECURITY_SAVED_REQUEST&quot;);
  
          if(defaultSavedRequest != null){
              getRedirectStrategy().sendRedirect(request, response, defaultSavedRequest.getRedirectUrl());
          }else{
              getRedirectStrategy().sendRedirect(request, response, &quot;http://localhost:7070/web&quot;);
          }
      }
  
  }
  </code></pre>
  <p>Gateway</p>
  <pre><code>    @EnableWebSecurity
  public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
      @Autowired
      private JwtConfig jwtConfig;
  
      @Value(&quot;${accessDeniedPage.url}&quot;)
      private String accessDeniedUrl;
  
      @Override
      protected void configure(final HttpSecurity http) throws Exception {
  
          http
                  .csrf().disable() // Disable CSRF (cross site request forgery)
  
                  // we use stateless session; session won't be used to store user's state.
                  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  
                  .and()
  
                  .formLogin()
                  .loginPage(&quot;/sso/login&quot;)
                  .permitAll()
  
                  .and()
  
                  // handle an authorized attempts
                  // If a user try to access a resource without having enough permissions
                  .exceptionHandling()
                  .accessDeniedPage(accessDeniedUrl)
                  //.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
  
                  .and()
  
                  // Add a filter to validate the tokens with every request
                  .addFilterBefore(new JwtTokenAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class)
  
                  // authorization requests config
                  .authorizeRequests()
  
                  .antMatchers(&quot;/web/**&quot;).hasAuthority(&quot;ADMIN&quot;)
  
                  // Any other request must be authenticated
                  .anyRequest().authenticated();
      }
  
  }
  
  @RequiredArgsConstructor
  public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
  
      private final JwtConfig jwtConfig;
  
      @Override
      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
              throws ServletException, IOException {
  
          // 1. get the authentication header. Tokens are supposed to be passed in the authentication header
          String header = request.getHeader(jwtConfig.getHeader());
  
          // 2. validate the header and check the prefix
          if(header == null || !header.startsWith(jwtConfig.getPrefix())) {
              chain.doFilter(request, response);        // If not valid, go to the next filter.
              return;
          }
  
          // If there is no token provided and hence the user won't be authenticated.
          // It's Ok. Maybe the user accessing a public path or asking for a token.
  
          // All secured paths that needs a token are already defined and secured in config class.
          // And If user tried to access without access token, then he/she won't be authenticated and an exception will be thrown.
  
          // 3. Get the token
          String token = header.replace(jwtConfig.getPrefix(), &quot;&quot;);
  
          try {  // exceptions might be thrown in creating the claims if for example the token is expired
  
  
              // 4. Validate the token
              Claims claims = Jwts.parser()
                                          .setSigningKey(jwtConfig.getSecret().getBytes())
                                          .parseClaimsJws(token)
                                          .getBody();
  
              String email = claims.get(&quot;email&quot;).toString();
  
              if(email != null) {
  
                  String[] authorities = ((String) claims.get(&quot;authorities&quot;)).split(&quot;,&quot;);
                  final List&lt;String&gt; listAuthorities = Arrays.stream(authorities).collect(Collectors.toList());
  
                  // 5. Create auth object
                  // UsernamePasswordAuthenticationToken: A built-in object, used by spring to represent the current authenticated / being authenticated user.
                  // It needs a list of authorities, which has type of GrantedAuthority interface, where SimpleGrantedAuthority is an implementation of that interface
                  final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                          email, null, listAuthorities
                          .stream()
                          .map(SimpleGrantedAuthority::new)
                          .collect(Collectors.toList()));
  
                  // 6. Authenticate the user
                  // Now, user is authenticated
                  SecurityContextHolder.getContext().setAuthentication(auth);
              }
  
          } catch (Exception e) {
              // In case of failure. Make sure it's clear; so guarantee user won't be authenticated
              SecurityContextHolder.clearContext();
          }
  
          // go to the next filter in the filter chain
          chain.doFilter(request, response);
      }
  }
  </code></pre>
  <hr />
  <pre><code>@Component
  public class AuthenticatedFilter extends ZuulFilter {
  
      @Override
      public String filterType() {
          return PRE_TYPE;
      }
  
      @Override
      public int filterOrder() {
          return 0;
      }
  
      @Override
      public boolean shouldFilter() {
          return true;
      }
  
      @Override
      public Object run() throws ZuulException {
  
          final Object object = SecurityContextHolder.getContext().getAuthentication();
          if (object == null || !(object instanceof UsernamePasswordAuthenticationToken)) {
              return null;
          }
  
          final UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
  
          final RequestContext requestContext = RequestContext.getCurrentContext();
  
          /*
          final AuthenticationDto authenticationDto = new AuthenticationDto();
          authenticationDto.setEmail(user.getPrincipal().toString());
          authenticationDto.setAuthenticated(true);
  
          authenticationDto.setRoles(user.getAuthorities()
                  .stream()
                  .map(GrantedAuthority::getAuthority)
                  .collect(Collectors.toList())); */
  
          try {
              //requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString(authenticationDto));
              requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString(&quot;authenticationDto&quot;));
          } catch (JsonProcessingException e) {
              throw new ZuulException(&quot;Error on JSON processing&quot;, 500, &quot;Parsing JSON&quot;);
          }
  
          return null;
      }
  }
  </code></pre>