java - 如何从Java Spring Boot中的请求标头获取不记名令牌?

Posted

技术标签:

【中文标题】java - 如何从Java Spring Boot中的请求标头获取不记名令牌?【英文标题】:How to get bearer token from header of a request in java spring boot? 【发布时间】:2021-03-09 04:49:53 【问题描述】:

您好,想要实现的是在 java spring boot RESTApi 控制器中获取从前端提交的不记名令牌,并使用 feign 客户端向另一个微服务发出另一个请求?这就是我的工作

上面的图片是我如何从邮递员那里收到我的请求,这是我的控制器代码:

@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”中我放了一个我质疑的字符串,我如何从邮递员那里得到它?

【问题讨论】:

哦,你现在遇到什么问题了? 如何获取邮递员(客户端)提交的不记名令牌值?将其作为字符串替换“tokenString”值,以便我可以使用它来提交另一个请求 您是否将此令牌用于任何其他目的?例如,在调用 Feign 客户端的应用程序中对用户进行身份验证? 是的,我用它来调用另一个使用 feign 客户端的微服务,并且在同一个网关中需要相同的令牌,因为它位于相同的 spring 安全性之后 我是否理解正确,您的第一个微服务,暴露“/store”端点,正在使用令牌进行身份验证?如果是这样,它是标准身份验证方案(例如 OAuth2)还是自定义方案(自定义 JWT 令牌身份验证)? 【参考方案1】:

我得到了答案,但我认为我仍然会等待更好的选择,因为我的答案是我必须在每个控制器中添加 @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,但我不知道该解决方案是否或如何正确使用它。如果有人可以用更好的方法做到这一点,我会接受你的答案

【讨论】:

【参考方案2】:

这里有几个选项。

例如,您可以使用request scoped bean,并按照您的建议使用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 *** question 中公开的解决方案。

老实说,我从未测试过它,但您似乎可以在 Feign 客户端定义中直接提供请求标头值,在您的情况下类似于:

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

当然,您也可以使用其他Controllers 可以扩展的通用Controller。这个Controller 将提供从Authorization 标头和提供的HTTP 请求获取承载令牌所需的逻辑,但我认为上述任何解决方案都更好。

【讨论】:

@KeVin 你能尝试任何建议的解决方案吗? 等我尝试另一种解决方案,但如果在我可以在这里选择一个解决方案之前赏金到期,我将再次开始赏金以奖励你们中的一个 非常感谢@KeVin,我真的很感激。 这是一个非常有帮助的答案。当我将 Feign Hystrix 与 Spring Boot 一起使用时,我遇到了一个更复杂的问题。对于那些有类似问题要解决的人,请查看这篇关于 Hystrix 和 Threadlocals medium.com/@saurav24081996/… 和 Hystrix 插件的文章 - github.com/Netflix/Hystrix/wiki/Plugins#concurrencystrategy 非常感谢@TonyMurphy,非常感谢您的评论。【参考方案3】:

我也遇到过类似的情况。我正在拦截来自一个微服务的请求,获取令牌并将其设置为我的新 ApiClient 并使用此 ApiClient 从另一个微服务调用端点。但我真的不知道是否有可能预先配置 feign 客户端。您可以做的一件事是创建 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); 时使用它,您可以从您的数据库(或从您保存它的任何其他地方)获取它并在此处设置。

【讨论】:

【参考方案4】:

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

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;

【讨论】:

【参考方案5】:

尽管建议的答案有效,但每次将令牌传递给 FeignClient 调用仍然不是最好的方法。 我建议为 feign 请求创建一个拦截器,在那里您可以从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());
       
      
    

这样你就可以为你的问题找到一个干净的解决方案

【讨论】:

【参考方案6】:

我认为@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一样

【讨论】:

以上是关于java - 如何从Java Spring Boot中的请求标头获取不记名令牌?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Boot 1.4 中自定义 Jackson

如何在 Spring Boot 的 YML 文件中创建条件属性?

java - 如何从Java Spring Boot中的请求标头获取不记名令牌?

Java Spring Boot 测试:如何从测试上下文中排除 java 配置类

如何从 Spring Security 中的 java 代码登录用户?

如何在 IDEA Intellij 上使用 Spring-boot 进行自动重新加载