如何从 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
可以扩展的公共 Controller
。 Controller
将提供从 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
}
你好,你想实现的是在 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
可以扩展的公共 Controller
。 Controller
将提供从 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
}