Comment obtenir un jeton de porteur à partir de l'en-tête d'une requête dans Java Spring Boot?

Nov 26 2020

Salut, ce que vous essayez de réaliser est d'obtenir le jeton de porteur qui a été soumis à partir du front-end dans le contrôleur java spring boot RESTApi et de faire une autre demande en utilisant un client feint à un autre microservices? voici ce que je fais

l'image ci-dessus montre comment je fais ma demande du facteur, et voici mon code de contrôleur:

@Operation(summary = "Save new")
@PostMapping("/store")
public ResponseEntity<ResponseRequest<TransDeliveryPlanning>> saveNewTransDeliveryPlanning(
        @Valid @RequestBody InputRequest<TransDeliveryPlanningDto> request) {

    TransDeliveryPlanning newTransDeliveryPlanning = transDeliveryPlanningService.save(request);

    ResponseRequest<TransDeliveryPlanning> response = new ResponseRequest<TransDeliveryPlanning>();

    if (newTransDeliveryPlanning != null) {
        response.setMessage(PESAN_SIMPAN_BERHASIL);
        response.setData(newTransDeliveryPlanning);
    } else {
        response.setMessage(PESAN_SIMPAN_GAGAL);
    }

    return ResponseEntity.ok(response);
}

et voici à quoi ressemble mon service:

public TransDeliveryPlanning save(InputRequest<TransDeliveryPlanningDto> request) {
       Future<List<PartnerDto>> initPartners = execs.submit(getDataFromAccount(transDeliveryPlanningDtSoDtoPartnerIdsSets));

}

public Callable<List<PartnerDto>> getDataFromAccount(Set<Long> ids) {

    String tokenString = "i should get the token from postman, how do i get it to here?";
    List<PartnerDto> partnerDtoResponse = accountFeignClient.getData("Bearer " + tokenString, ids);
    
    return () -> partnerDtoResponse;
}

comme vous pouvez le voir, dans "tokenString", j'ai mis une chaîne sur laquelle je me suis interrogé, comment puis-je l'obtenir à partir du facteur?

Réponses

10 stacker Dec 03 2020 at 14:58

Bien que les réponses suggérées fonctionnent, transmettre le jeton à chaque fois aux FeignClientappels n'est toujours pas la meilleure façon de le faire. Je suggérerais de créer un intercepteur pour les demandes de simulation et là, vous pouvez extraire le jeton RequestContextHolderet l'ajouter directement à l'en-tête de la demande. comme ça:

    @Component
    public class FeignClientInterceptor implements RequestInterceptor {
    
      private static final String AUTHORIZATION_HEADER = "Authorization";

      public static String getBearerTokenHeader() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("Authorization");
      }
    
      @Override
      public void apply(RequestTemplate requestTemplate) {

          requestTemplate.header(AUTHORIZATION_HEADER, getBearerTokenHeader());
       
      }
    }

de cette façon, vous avez une solution propre à votre problème

4 jccampanero Nov 29 2020 at 18:31

Vous avez plusieurs options ici.

Par exemple, vous pouvez utiliser un bean à portée de requête et, comme vous le suggérez, un intercepteur MVC .

Fondamentalement, vous devez définir un wrapper pour la valeur du jeton:

public class BearerTokenWrapper {
   private String token;

   // setters and getters
}

Ensuite, fournissez une implémentation d'un MVC HandlerInterceptor:

public class BearerTokenInterceptor extends HandlerInterceptorAdapter {

  private BearerTokenWrapper tokenWrapper;

  public BearerTokenInterceptor(BearerTokenWrapper tokenWrapper) {
    this.tokenWrapper = tokenWrapper;
  }

  @Override
  public boolean preHandle(HttpServletRequest request,
          HttpServletResponse response, Object handler) throws Exception {
    final String authorizationHeaderValue = request.getHeader("Authorization");
    if (authorizationHeaderValue != null && authorizationHeaderValue.startsWith("Bearer")) {
      String token = authorizationHeaderValue.substring(7, authorizationHeaderValue.length());
      tokenWrapper.setToken(token);
    }
    
    return true;
  }
}

Cet intercepteur doit être enregistré dans votre configuration MVC. Par exemple:

@EnableWebMvc
@Configuration
public class WebConfiguration extends WebConfigurer { /* or WebMvcConfigurerAdapter for Spring 4 */

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(bearerTokenInterceptor());
  }

  @Bean
  public BearerTokenInterceptor bearerTokenInterceptor() {
      return new BearerTokenInterceptor(bearerTokenWrapper());
  }

  @Bean
  @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
  public BearerTokenWrapper bearerTokenWrapper() {
    return new BearerTokenWrapper();
  }

}

Avec cette configuration, vous pouvez utiliser le bean dans votre Serviceautowiring le bean correspondant:

@Autowired
private BearerTokenWrapper tokenWrapper;

//...


public TransDeliveryPlanning save(InputRequest<TransDeliveryPlanningDto> request) {
       Future<List<PartnerDto>> initPartners = execs.submit(getDataFromAccount(transDeliveryPlanningDtSoDtoPartnerIdsSets));

}

public Callable<List<PartnerDto>> getDataFromAccount(Set<Long> ids) {

    String tokenString = tokenWrapper.getToken();
    List<PartnerDto> partnerDtoResponse = accountFeignClient.getData("Bearer " + tokenString, ids);
    
    return () -> partnerDtoResponse;
}

Des solutions similaires ont été fournies ici dans le débordement de pile. Voir, par exemple, cette question connexe .

En plus de cette approche basée sur Spring, vous pouvez essayer quelque chose de similaire à la solution exposée dans cette autre question de stackoverflow .

Honnêtement, je ne l'ai jamais testé, mais il semble que vous puissiez fournir la valeur de l'en-tête de la demande directement dans la définition du client Feign, dans votre cas, quelque chose comme:

@FeignClient(name="AccountFeignClient")
public interface AccountFeignClient {    
    @RequestMapping(method = RequestMethod.GET, value = "/data")
    List<PartnerDto> getData(@RequestHeader("Authorization") String token, Set<Long> ids);
}

Bien sûr, vous pouvez également une commune Controllerque d'autres Controllerpeuvent étendre. Cela Controllerfournira la logique nécessaire pour obtenir le jeton de support à partir de l'en- Authorizationtête et de la requête HTTP fournie, mais à mon avis, toutes les solutions susmentionnées sont meilleures.

3 f.trajkovski Nov 29 2020 at 19:06

J'ai eu un cas similaire. J'interceptais les demandes d'un microservice, obtenais le jeton et le définissais mon nouveau ApiClient et appelais le point de terminaison d'un autre microservice utilisant cet ApiClient. Mais je ne sais vraiment pas s'il est possible de préconfigurer le client feign. Une chose que vous pouvez faire est de créer DefaultApiFilter, d'intercepter la demande, d'enregistrer le jeton dans votre base de données (ou de le définir sur une variable statique, une classe singleton ou quelque chose de similaire), puis appeler votre méthode de service lorsque vous essayez d'utiliser le FeignClient:

package com.north.config;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class DefaultApiFilter implements Filter {


@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) servletRequest;

    String auth = req.getHeader("Authorization");

    //TODO if you want you can persist your token here and use it on other place

    //TODO This may be used for verification if it comes from the right endpoint and if you should save the token
    final String requestURI = ((RequestFacade) servletRequest).getRequestURI();

    filterChain.doFilter(servletRequest, servletResponse);
    }
}

Cette doFilterméthode sera toujours exécutée avant qu'un point de terminaison ne soit appelé, et plus tard, le point de terminaison sera appelé.

Et plus tard, utilisez-le lorsque vous appelez le, accountFeignClient.getData("Bearer " + tokenString, ids);vous pouvez l'obtenir à partir de votre base de données (ou de tout autre endroit où vous l'avez conservé) et le définir ici.

1 KeVin Nov 29 2020 at 13:53

J'ai eu la réponse mais je pense que j'attendrai toujours une meilleure option, car ma réponse ici est que je dois ajouter @RequestHeader dans chaque contrôleur pour obtenir la valeur de mon jeton et obtenir le jeton avec String token = headers.getFirst(HttpHeaders.AUTHORIZATION);, et voici mon contrôleur complet:

@Operation(summary = "Save new")
@PostMapping("/store")
public ResponseEntity<ResponseRequest<TransDeliveryPlanning>> saveNewTransDeliveryPlanning(@RequestHeader HttpHeaders headers, 
        @Valid @RequestBody InputRequest<TransDeliveryPlanningDto> request) {

    String token = headers.getFirst(HttpHeaders.AUTHORIZATION);

    TransDeliveryPlanning newTransDeliveryPlanning = transDeliveryPlanningService.save(token, request);

    ResponseRequest<TransDeliveryPlanning> response = new ResponseRequest<TransDeliveryPlanning>();

    if (newTransDeliveryPlanning != null) {
        response.setMessage(PESAN_SIMPAN_BERHASIL);
        response.setData(newTransDeliveryPlanning);
    } else {
        response.setMessage(PESAN_SIMPAN_GAGAL);
    }

    return ResponseEntity.ok(response);
}

et j'ai lu quelque part il y a quelque chose qui s'appelle Interceptordonc nous n'avons pas à taper @RequestHeader dans chaque contrôleur je pense, mais je ne sais pas si c'est la solution ou comment l'utiliser correctement. si quelqu'un peut faire cela avec quelque chose de mieux, j'accepterai la vôtre comme réponse

Abhijay Dec 02 2020 at 07:51

Vous pouvez créer cette méthode statique simple dans une classe d'utilitaire et réutiliser cette méthode directement.

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class BearerTokenUtil {

  public static String getBearerTokenHeader() {
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("Authorization");
  }
}

Votre service ressemblerait alors à ceci

public TransDeliveryPlanning save(InputRequest<TransDeliveryPlanningDto> request) {
       Future<List<PartnerDto>> initPartners = execs.submit(getDataFromAccount(transDeliveryPlanningDtSoDtoPartnerIdsSets));
}

public Callable<List<PartnerDto>> getDataFromAccount(Set<Long> ids) {
    List<PartnerDto> partnerDtoResponse = accountFeignClient.getData(BearerTokenUtil.getBearerTokenHeader(), ids);
    return () -> partnerDtoResponse;
}
RandyHector Dec 04 2020 at 18:19

Je pense que la réponse ci-dessous de @stacker est correcte, mais je pense aussi qu'elle est en quelque sorte incomplète et manque le "comment l'utiliser dans Feign".

Pour les besoins de l'exemple, je vais fournir un cas d'utilisation réel où vous pouvez intercepter User-Agentl'appelant vers votre service et le transférer dans l' Feignappel

En supposant que vous utilisez des Feignclients basés sur des annotations, voici comment vous pouvez utiliser l'intercepteur dans tous vos appels clients Feign sans code supplémentaire

@Configuration
@EnableFeignClients(
    defaultConfiguration = DefaultFeignConfiguration.class
)
public class FeignConfig
{
}
@Configuration
@Import(FeignClientsConfiguration.class)
public class DefaultFeignConfiguration
{
    @Bean
    public RequestInterceptor userAgentHeaderInterceptor() {
        return UserAgentHeaderInterceptor();
    } 
}

C'est la classe d'intercepteur User-Agent

public class UserAgentHeaderInterceptor extends BaseHeaderInterceptor
{

    private static final String USER_AGENT = "User-Agent";


    public UserAgentHeaderInterceptor()
    {
        super(USER_AGENT);
    }
}
public class BaseHeaderInterceptor implements RequestInterceptor
{

    private final String[] headerNames;


    public BaseHeaderInterceptor(String... headerNames)
    {
        this.headerNames = headerNames;
    }


    @Override
    public void apply(RequestTemplate template)
    {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        if (attributes != null)
        {
            HttpServletRequest httpServletRequest = attributes.getRequest();

            for (String headerName : headerNames)
            {
                String headerValue = httpServletRequest.getHeader(headerName);
                if (headerValue != null && !headerValue.isEmpty())
                {
                    template.header(headerName, headerValue);
                }
            }
        }
    }
}

Dans votre cas, il vous suffit de prendre cette classe de base et de créer votre propre intercepteur de la même manière que le UserAgentHeaderInterceptor