如何从 java spring 引导中的请求的 header 获取承载令牌?

How to get bearer token from header of a request in java spring boot?

你好,你想实现的是在 java spring 中获取从前端提交的不记名令牌引导 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”中我放了一个我质疑的字符串,我如何从邮递员那里得到它?

我得到了答案,但我认为我仍然会等待更好的选择,因为我在这里的答案是我必须在每个控制器中添加 @RequestHeader 以获取我的令牌的值并使用 [=11 获取令牌=],这是我的完整控制器:

@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,但我不知道这是否是解决方案或如何正确使用它。如果有人可以用更好的方法做到这一点,我会接受你的答案

这里有几个选项。

例如,您可以使用 request scoped bean and, as you suggest, one MVC interceptor.

基本上,您需要为令牌值定义一个包装器:

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

此处堆栈溢出中提供了类似的解决方案。例如,参见 this related question.

除了这种基于 Spring 的方法之外,您还可以尝试类似于 this other Whosebug question 中公开的解决方案的方法。

老实说我从来没有测试过它,但似乎你可以在 Feign 客户端定义中提供请求 header 值,在你的情况下是这样的:

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

当然,您也可以使用其他 Controller 可以扩展的公共 ControllerController 将提供从 Authorization header 和提供的 HTTP 请求中获取承载令牌所需的逻辑,但在我看来,上述任何解决方案都更好。

我有一个类似的案例。我正在拦截来自一个微服务的请求,获取令牌并将其设置为我的新 ApiClient 并使用此 ApiClient 从另一个微服务调用端点。但我真的不知道是否有可能预先配置假客户端。您可以做的一件事是创建 DefaultApiFilter,拦截请求,将令牌保存在您的数据库中(或将其设置为某个静态变量、某个单例 class 或类似的东西),然后在其中调用您的服务方法尝试使用 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); 时使用它,您可以从您的数据库(或从您保存它的任何其他地方)获取它并在此处设置它。

您可以在实用程序中创建这个简单的静态方法 class,然后直接重用这个方法。

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

虽然建议的答案有效,但每次将令牌传递给 FeignClient 调用仍然不是最好的方法。 我建议为伪装请求创建一个拦截器,在那里你可以从 RequestContextHolder 中提取令牌并将其直接添加到请求 header 中。 像这样:

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

这样你就有了一个干净的问题解决方案

我认为下面@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();
    } 
}

这是用户代理拦截器class

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

在你的情况下,你只需要采用这个基础 class 并以与 UserAgentHeaderInterceptor

相同的方式创建你自己的拦截器

从 header 获取 Bearer Token 的简单方法是使用 @RequestHeader 和 header 名字.

请参阅下面的代码示例

@PostMapping("/some-endpoint")
public ResponseEntity<String> someClassNmae(@RequestHeader("Authorization") String bearerToken) {

   System.out.println(bearerToken); // print out bearer token

   // some more code
}