定时器(@Scheduled)使用feign夸服务调用,Oauth2 客户端client_credentials模式
应用场景
定时器(@Scheduled)使用feign夸服务调用(A——>B)。因为不是web请求,所以没有HTTP 请求信息,所以:ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); 获取不到token。报null值异常。
(PS:我的A服务是专门做定时器的服务,不走任何web请求!)
解决办法(仅代表个人)
我在A服务调用B服务之前加了一个私有的feign拦截器(写在了A服务里面):使用Oauth2的客户端(client_credentials)模式获取到token并塞进了请求头中。
参数说明
/oauth/token 是Oauth2 提供的token获取地址
grant_type=client_credentials 代表客户端模式请求token
client_id=schedule “schedule”数据库设置的
client_secret=123456 “123456 ”数据库设置的
scope 代表权限,不填代表所有
package cn.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Map;
/**
* 实现RequestInterceptor接口:
* feign全局设置求头信息
*/
@Configuration
public class MyFeignRequestInterceptor implements RequestInterceptor {
@Resource
RestTemplate restTemplate;
@Override
public void apply(RequestTemplate requestTemplate) {
Map<String,Object> objectMap = restTemplate.getForObject("http://jhcloud-portal/oauth/token?grant_type=client_credentials&client_id=schedule&client_secret=123456",Map.class);
String token = objectMap.get("access_token").toString();
requestTemplate.header("Authorization", "bearer "+token);
}
}
然后在全局的feignConfig中加个判断
package cn.jhcloud_common.config;
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
if (attributes == null) {
//因为私有的feign拦截器,所以请求头里已经有了token信息了,就不需要在这里在做处理了!
}else {
HttpServletRequest request = attributes.getRequest();
requestTemplate.header(HttpHeaders.AUTHORIZATION, request.getHeader(HttpHeaders.AUTHORIZATION));
}
//seata分布式事务
requestTemplate.header(RootContext.KEY_XID, RootContext.getXID());
}
}
使用 RestTemplate调用的时候最好设置一个全局的:RestTemplateConfig,不然可能会报错。
package cn.jhcloud_common.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced //让这个RestTemplate在请求时拥有客户端负载均衡的能力
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
yml 里面最好也设置一下,如果不设置的话。会报:I/O ERROR ON GET REQUEST FOR "http://jhcloud-portal/auth/currentUser":
security:
oauth2:
resource:
#每次调用接口都获取一下当前登录人的信息
user-info-uri: http://jhcloud-portal/auth/currentUser
loadBalanced: true #让这个RestTemplate在请求时拥有客户端负载均衡的能力
- [ 1、] 可能存在的问题,因为yml里面设置了每次调用接口之前都需要先获取一下当前登录人的信息,然而客户端模式获取到的token,不是用户token,所以没有用户信息,相应的也没有用户权限。所以postmain可能会"无效的token"错误。根据控制台报错信息定位一下会发现是:FixedAuthoritiesExtractor.extractAuthorities 报错
所以你可能需要在获取用户信息的地方,“/currentUser”。自己封装一下权限
然后正常调用就可以了。
因为时间有点长,遇到的其他问题也忘了。先写这么多吧。想起来再补充.