Java Spring Bootのリクエストのヘッダーからベアラートークンを取得するにはどうすればよいですか?

Nov 26 2020

こんにちは、Java Spring Boot RESTApiコントローラーのフロントエンドから送信されたベアラートークンを取得し、偽のクライアントを使用して別のマイクロサービスに別のリクエストを実行しようとしていますか?これが私がすることです

上の画像は、郵便配達員からのリクエストの方法です。コントローラーコードは次のとおりです。

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

これが私のサービスの様子です:

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

ご覧のとおり、「tokenString」に質問した文字列を入れました。郵便配達員からそこに到達するにはどうすればよいですか。

回答

10 stacker Dec 03 2020 at 14:58

提案された回答は機能しますが、FeignClient呼び出しに毎回トークンを渡すことは、それを行うための最良の方法ではありません。偽のリクエスト用のインターセプターを作成することをお勧めします。そこでトークンを抽出しRequestContextHolderて、リクエストヘッダーに直接追加できます。このような:

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

このようにして、問題に対するクリーンなソリューションが得られます

4 jccampanero Nov 29 2020 at 18:31

ここにはいくつかのオプションがあります。

たとえば、リクエストスコープのBeanと、ご提案のとおり、1つのMVCインターセプターを使用できます。

基本的に、トークン値のラッパーを定義する必要があります。

public class BearerTokenWrapper {
   private String token;

   // setters and getters
}

次に、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;
  }
}

このインターセプターは、MVC構成に登録する必要があります。例えば:

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

}

この設定ではService、対応するBeanの自動配線でBeanを使用できます。

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

同様のソリューションがスタックオーバーフローで提供されています。たとえば、この関連する質問を参照してください。

このSpringベースのアプローチに加えて、この他のスタックオーバーフローの質問で公開されているソリューションと同様のことを試すことができます。

正直なところ、私はそれをテストしたことがありませんが、Feignクライアント定義でリクエストヘッダー値を提供できるようです。あなたの場合は次のようになります。

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

もちろん、ControllerControllerのが拡張できる共通点もあります。これControllerにより、Authorizationヘッダーからベアラートークンを取得するために必要なロジックと、提供されたHTTPリクエストが提供されますが、私の意見では、前述のソリューションのいずれかが優れています。

3 f.trajkovski Nov 29 2020 at 19:06

私も同様のケースがありました。あるマイクロサービスからのリクエストをインターセプトし、トークンを取得して新しいApiClientに設定し、このApiClientを使用して別のマイクロサービスからエンドポイントを呼び出していました。しかし、偽のクライアントを事前設定する可能性があるかどうかは本当にわかりません。実行できることの1つは、DefaultApiFilterを作成し、リクエストをインターセプトし、トークンをデータベースに保存して(または、静的変数、シングルトンクラスなどに設定して)、サービスメソッドを呼び出して使用することです。 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);
    }
}

このdoFilterメソッドは、エンドポイントが呼び出される前に常に実行され、後でエンドポイントが呼び出されます。

後で呼び出すときに使用してaccountFeignClient.getData("Bearer " + tokenString, ids);、データベース(または保存した他の場所)から取得して、ここに設定できます。

1 KeVin Nov 29 2020 at 13:53

私は答えを得ましたが、ここでの私の答えは、トークンの値を取得してトークンを取得するためにすべてのコントローラーに@RequestHeaderを追加する必要があるため、まだより良いオプションを待つと思います。これString token = headers.getFirst(HttpHeaders.AUTHORIZATION);が私の完全なコントローラーです:

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

どこかで何かと呼ばれるものを読んだInterceptorので、私が思うすべてのコントローラーに@RequestHeaderと入力する必要はありませんが、それが解決策であるか、それを適切に使用する方法かはわかりません。誰かがもっと良いことでこれを行うことができれば、私はあなたの答えを受け入れます

Abhijay Dec 02 2020 at 07:51

この単純な静的メソッドをユーティリティクラスで作成し、このメソッドを直接再利用できます。

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

その場合、サービスは次のようになります

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

以下の@stackerからの答えは正しいと思いますが、どういうわけか不完全で、「Feignでの使用方法」が欠落していると思います。

例として、User-Agent発信者のをサービスに傍受し、これをFeign通話で転送できる実際のユースケースを提供します。

Feignアノテーションに基づいてクライアントを使用すると仮定すると、これは、追加のコードなしですべてのFeignクライアント呼び出しでインターセプターを使用できる方法です。

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

これは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);
                }
            }
        }
    }
}

あなたの場合、この基本クラスを取得して、と同じ方法で独自のインターセプターを作成する必要があります。 UserAgentHeaderInterceptor