Wie bekomme ich einen Inhaber-Token aus dem Header einer Anfrage in Java Spring Boot?

Nov 26 2020

Hallo, was Sie erreichen möchten, ist, ein Inhaber-Token zu erhalten, das vom Front-End im Java Spring Boot RESTApi-Controller gesendet wurde, und eine weitere Anfrage mit dem Feign-Client an einen anderen Microservices zu senden. Hier ist was ich tue

Bild oben ist, wie ich meine Anfrage vom Postboten mache, und hier ist mein Controller-Code:

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

und so sieht mein Service aus:

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

Wie Sie sehen können, habe ich in "tokenString" dort eine Zeichenfolge eingefügt, über die ich Fragen gestellt habe. Wie komme ich vom Postboten dorthin?

Antworten

10 stacker Dec 03 2020 at 14:58

Obwohl die vorgeschlagenen Antworten funktionieren, ist es FeignClientimmer noch nicht der beste Weg , das Token jedes Mal an Anrufe zu übergeben. Ich würde vorschlagen, einen Interceptor für vorgetäuschte Anforderungen zu erstellen. Dort können Sie das Token extrahieren RequestContextHolderund direkt zum Anforderungsheader hinzufügen. so was:

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

Auf diese Weise haben Sie eine saubere Lösung für Ihr Problem

4 jccampanero Nov 29 2020 at 18:31

Sie haben hier mehrere Möglichkeiten.

Sie können beispielsweise eine Bean mit Anforderungsbereich und, wie Sie vorschlagen, einen MVC-Interceptor verwenden .

Grundsätzlich müssen Sie einen Wrapper für den Token-Wert definieren:

public class BearerTokenWrapper {
   private String token;

   // setters and getters
}

Stellen Sie dann eine Implementierung einer MVC bereit 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;
  }
}

Dieser Interceptor sollte in Ihrer MVC-Konfiguration registriert sein. Zum Beispiel:

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

}

Mit diesem Setup können Sie die Bean in Ihrem ServiceAutowire für die entsprechende Bean verwenden:

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

Ähnliche Lösungen wurden hier im Stapelüberlauf bereitgestellt. Siehe zum Beispiel diese verwandte Frage .

Zusätzlich zu diesem Spring-basierten Ansatz können Sie etwas Ähnliches wie die in dieser anderen Frage zum Stapelüberlauf dargestellte Lösung ausprobieren .

Ehrlich gesagt habe ich es noch nie getestet, aber es scheint, dass Sie den Wert des Anforderungsheaders direkt in der Feign-Client-Definition angeben können, in Ihrem Fall wie folgt:

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

Natürlich können Sie auch eine gemeinsame Controller, die andere Controllers erweitern können. Dies Controllerliefert die Logik, die erforderlich ist, um das Inhaber-Token aus dem AuthorizationHeader und der bereitgestellten HTTP-Anforderung zu erhalten, aber meiner Meinung nach ist jede der oben genannten Lösungen besser.

3 f.trajkovski Nov 29 2020 at 19:06

Ich hatte einen ähnlichen Fall. Ich habe die Anforderungen von einem Microservice abgefangen, das Token abgerufen, meinen neuen ApiClient festgelegt und den Endpunkt von einem anderen Microservice mit diesem ApiClient aufgerufen. Aber ich weiß wirklich nicht, ob es eine Möglichkeit gibt, einen falschen Client vorkonfigurieren zu können. Sie können DefaultApiFilter erstellen, die Anforderung abfangen, das Token in Ihrer Datenbank speichern (oder auf eine statische Variable, eine Singleton-Klasse oder ähnliches setzen) und dann Ihre Dienstmethode aufrufen, wenn Sie versuchen, das zu verwenden 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);
    }
}

Diese doFilterMethode wird immer ausgeführt, bevor ein Endpunkt aufgerufen wird, und später wird der Endpunkt aufgerufen.

Und später beim Aufrufen des accountFeignClient.getData("Bearer " + tokenString, ids);können Sie es aus Ihrer Datenbank (oder von einem anderen Ort, an dem Sie es aufbewahrt haben) abrufen und hier einstellen.

1 KeVin Nov 29 2020 at 13:53

Ich habe die Antwort bekommen, aber ich denke, dort werde ich immer noch auf eine bessere Option warten, da meine Antwort hier ist, dass ich @RequestHeader in jeden Controller einfügen muss, um den Wert meines Tokens zu erhalten und das Token mit zu erhalten String token = headers.getFirst(HttpHeaders.AUTHORIZATION);, und hier ist mein vollständiger Controller:

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

und ich habe irgendwo gelesen, dass es etwas gibt, das heißt, Interceptoralso müssen wir nicht @RequestHeader in jeden Controller eingeben, den ich denke, aber ich weiß nicht, ob das die Lösung ist oder wie man es richtig verwendet. Wenn jemand dies mit etwas Besserem tun kann, werde ich Ihre als Antwort akzeptieren

Abhijay Dec 02 2020 at 07:51

Sie können diese einfache statische Methode in einer Dienstprogrammklasse erstellen und diese Methode direkt wiederverwenden.

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

Ihr Service würde dann so aussehen

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

Ich denke, die Antwort von @stacker unten ist richtig, aber ich denke auch, dass sie irgendwie unvollständig ist und das "Wie man es in Feign verwendet" fehlt.

Aus Gründen der diesem Beispiel werde ich einen realen Anwendungsfall an , wo Sie die abfangen User-Agentdes Anrufers zu Ihren Diensten und leitet diese in den FeignAufruf

Angenommen, Sie verwenden FeignClients basierend auf Anmerkungen. Auf diese Weise können Sie den Interceptor in all Ihren Feign-Clientaufrufen ohne zusätzlichen Code verwenden

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

Dies ist die User-Agent-Interceptor-Klasse

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

In Ihrem Fall müssen Sie entweder diese Basisklasse nehmen und Ihren eigenen Interceptor auf die gleiche Weise wie den erstellen UserAgentHeaderInterceptor