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);
当然,您也可以使用其他Controller
s 可以扩展的通用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 配置类