Jak uzyskać token okaziciela z nagłówka żądania w rozruchu java Spring?

Nov 26 2020

Cześć, co próbujesz osiągnąć, to uzyskać token okaziciela, który został przesłany z interfejsu użytkownika w kontrolerze RESTApi rozruchu java spring boot i wykonać kolejne żądanie za pomocą klienta fałszywego do innej mikrousługi? oto co robię

obrazek powyżej pokazuje, jak mogę wykonać moją prośbę od listonosza, a oto mój kod kontrolera:

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

a tak wygląda moja usługa:

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

jak widzisz, w "tokenString" umieściłem ciąg, o który pytałem, jak mogę go tam dostać od listonosza?

Odpowiedzi

10 stacker Dec 03 2020 at 14:58

Chociaż sugerowane odpowiedzi działają, przekazywanie tokena za każdym razem do FeignClientpołączeń nadal nie jest najlepszym sposobem na zrobienie tego. Sugerowałbym utworzenie przechwytywacza dla żądań pozorowanych i tam można wyodrębnić token z RequestContextHolderi dodać go bezpośrednio do nagłówka żądania. lubię to:

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

w ten sposób masz czyste rozwiązanie swojego problemu

4 jccampanero Nov 29 2020 at 18:31

Masz tutaj kilka opcji.

Na przykład można użyć komponentu bean o zasięgu żądania i, jak sugerujesz, jednego przechwytywacza MVC .

Zasadniczo musisz zdefiniować opakowanie dla wartości tokenu:

public class BearerTokenWrapper {
   private String token;

   // setters and getters
}

Następnie zapewnij implementację 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;
  }
}

Ten przechwytywacz powinien być zarejestrowany w konfiguracji MVC. Na przykład:

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

}

Dzięki tej konfiguracji możesz użyć ziarna do automatycznego Servicepodłączania odpowiedniego ziarna:

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

Podobne rozwiązania zastosowano tutaj w przypadku przepełnienia stosu. Zobacz na przykład to powiązane pytanie .

Oprócz tego podejścia opartego na Spring, możesz spróbować czegoś podobnego do rozwiązania przedstawionego w tym innym pytaniu o przepełnienie stosu .

Szczerze mówiąc, nigdy tego nie testowałem, ale wydaje się, że możesz podać wartość nagłówka żądania bezpośrednio w definicji klienta Feign, w twoim przypadku coś takiego:

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

Oczywiście, można również częstym Controllerże inne Controllers może przedłużyć. To Controllerzapewni logikę niezbędną do uzyskania okaziciela Reklamowe z Authorizationnagłówkiem i żądania HTTP dostarczonych, ale moim zdaniem każdy z wyżej wymienionych rozwiązań są lepsze.

3 f.trajkovski Nov 29 2020 at 19:06

Miałem podobny przypadek. Przechwytywałem żądania z jednej mikrousługi, pobierałem token i ustawiałem go jako mój nowy ApiClient i wywoływałem punkt końcowy z innej mikrousługi przy użyciu tego ApiClient. Ale naprawdę nie wiem, czy istnieje możliwość wstępnej konfiguracji pozorowanego klienta. Jedną z rzeczy, które możesz zrobić, to utworzyć DefaultApiFilter, przechwycić żądanie, zapisać token w swojej bazie danych (lub ustawić go na jakąś zmienną statyczną, jakąś klasę pojedynczą lub coś podobnego), a następnie wywołać w niej swoją metodę usługi podczas próby użycia 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);
    }
}

Ta doFiltermetoda będzie zawsze wykonywana przed wywołaniem dowolnego punktu końcowego, a później zostanie wywołany punkt końcowy.

A później użyj go, dzwoniąc accountFeignClient.getData("Bearer " + tokenString, ids);, możesz go pobrać ze swojej bazy danych (lub z dowolnego innego miejsca, w którym ją przechowujesz) i ustawić tutaj.

1 KeVin Nov 29 2020 at 13:53

Mam odpowiedź, ale myślę, że nadal będę czekać na lepszą opcję, ponieważ moja odpowiedź brzmi: muszę dodać @RequestHeader do każdego kontrolera, aby uzyskać wartość mojego tokena i uzyskać token String token = headers.getFirst(HttpHeaders.AUTHORIZATION);, a oto mój kompletny kontroler:

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

i czytałem gdzieś, że jest coś Interceptortakiego, że nie musimy wpisywać @RequestHeader w każdym kontrolerze, o którym myślę, ale nie wiem, czy to rozwiązanie i jak go poprawnie używać. jeśli ktoś może to zrobić za pomocą czegoś lepszego, przyjmuję twoją odpowiedź

Abhijay Dec 02 2020 at 07:51

Możesz utworzyć tę prostą statyczną metodę w klasie narzędziowej i bezpośrednio użyć tej metody ponownie.

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

Twoja usługa będzie wtedy wyglądać tak

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

Myślę, że poniższa odpowiedź z @stacker jest poprawna, ale myślę, że jest w jakiś sposób niekompletna i brakuje jej „jak go używać w Feign”.

Dla przykładu przedstawię prawdziwy przypadek użycia, w którym możesz przechwycić User-Agentdzwoniącego do swojej usługi i przekazać to w Feignpołączeniu

Zakładając, że używasz Feignklientów na podstawie adnotacji, w ten sposób możesz używać przechwytywacza we wszystkich połączeniach klientów Feign bez dodatkowego kodu

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

To jest klasa przechwytująca 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);
                }
            }
        }
    }
}

W twoim przypadku wystarczy wziąć tę klasę bazową i utworzyć własny przechwytywacz w taki sam sposób jak UserAgentHeaderInterceptor