Como obter o token do portador do cabeçalho de uma solicitação no java spring boot?

Nov 26 2020

Oi, o que estou tentando alcançar é obter o token do portador enviado do front-end no controlador RESTApi do java spring boot e fazer outra solicitação usando o cliente fingir para outro microsserviço? aqui está o que eu faço

imagem acima é como eu faço minha solicitação do carteiro, e aqui está meu código de controlador:

@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);
}

e aqui está como meu serviço se parece:

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;
}

como você pode ver, em "tokenString" coloquei uma string que questionei, como faço para trazê-la do carteiro?

Respostas

10 stacker Dec 03 2020 at 14:58

Embora as respostas sugeridas funcionem, passar o token a cada vez para FeignClientchamadas ainda não é a melhor maneira de fazê-lo. Eu sugeriria criar um interceptor para solicitações de simulação e lá você pode extrair o token RequestContextHoldere adicioná-lo ao cabeçalho da solicitação diretamente. como isso:

    @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());
       
      }
    }

desta forma, você tem uma solução limpa para o seu problema

4 jccampanero Nov 29 2020 at 18:31

Você tem várias opções aqui.

Por exemplo, você pode usar um bean de escopo de solicitação e, como você sugere, um interceptor MVC .

Basicamente, você precisa definir um wrapper para o valor do token:

public class BearerTokenWrapper {
   private String token;

   // setters and getters
}

Em seguida, forneça uma implementação de um 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;
  }
}

Este interceptor deve ser registrado em sua configuração MVC. Por exemplo:

@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();
  }

}

Com esta configuração, você pode usar o bean em seu Serviceautowiring o bean correspondente:

@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;
}

Soluções semelhantes foram fornecidas aqui no estouro de pilha. Veja, por exemplo, esta questão relacionada .

Além dessa abordagem baseada em Spring, você pode tentar algo semelhante à solução exposta nesta outra questão stackoverflow .

Honestamente, eu nunca testei, mas parece que você pode fornecer o valor do cabeçalho da solicitação diretamente na definição do cliente Feign, no seu caso algo como:

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

Claro, você também pode ter um comum Controllerque outros programas Controllerpodem estender. Isso Controllerfornecerá a lógica necessária para obter o token do portador do Authorizationcabeçalho e da solicitação HTTP fornecida, mas, em minha opinião, qualquer uma das soluções mencionadas é melhor.

3 f.trajkovski Nov 29 2020 at 19:06

Eu tive um caso semelhante. Eu estava interceptando as solicitações de um microsserviço, obtendo o token e configurando-o como meu novo ApiClient e chamando o endpoint de outro microsserviço usando este ApiClient. Mas eu realmente não sei se existe a possibilidade de pré-configurar o cliente fingido. Uma coisa que você pode fazer é criar DefaultApiFilter, interceptar a solicitação, salvar o token em seu banco de dados (ou configurá-lo para alguma variável estática, alguma classe singleton ou algo semelhante) e, em seguida, chamar seu método de serviço ao tentar usar o 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);
    }
}

Este doFiltermétodo sempre será executado antes que qualquer terminal seja chamado e, posteriormente, o terminal será chamado.

E depois use-o ao chamar o accountFeignClient.getData("Bearer " + tokenString, ids);você pode obtê-lo de seu banco de dados (ou de qualquer outro lugar que você o manteve) e configurá-lo aqui.

1 KeVin Nov 29 2020 at 13:53

recebi a resposta, mas acho que ainda vou esperar por uma opção melhor, pois minha resposta aqui é que tenho que adicionar @RequestHeader em cada controlador para obter o valor do meu token e obter o token com String token = headers.getFirst(HttpHeaders.AUTHORIZATION);, e aqui está meu controlador completo:

@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);
}

e eu li em algum lugar que há algo chamado, Interceptorentão não precisamos digitar @RequestHeader em cada controlador, eu acho, mas não sei se essa é a solução ou como usá-la corretamente. se alguem puder fazer isso com algo melhor, vou aceitar a sua como resposta

Abhijay Dec 02 2020 at 07:51

Você pode criar esse método estático simples em uma classe de utilitário e reutilizar esse método diretamente.

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");
  }
}

Seu serviço ficaria assim

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

Acho que a resposta de @stacker abaixo está correta, mas também acho que está de alguma forma incompleta e faltando "como usar no Feign".

Para fins de exemplo, vou fornecer um caso de uso real em que você pode interceptar o User-Agentdo chamador para o seu serviço e encaminhá-lo na Feignchamada

Supondo que você use Feignclientes com base em anotações, é assim que você pode usar o interceptor em todas as suas chamadas de clientes Feign sem nenhum código extra

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

Esta é a classe do interceptor 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);
                }
            }
        }
    }
}

No seu caso, você só precisa pegar essa classe base e criar seu próprio interceptor da mesma forma que o UserAgentHeaderInterceptor