定时任务调用OpenFegin无token认证异常
问题
定时任务job中有调用其他服务,由于不是通过接口进来的,属于是未登录状态,如果这时候调用openfegin的接口,这个请求是没有token的,会抛出没有权限的异常。
分析
项目中OpenFegin默认是通过RequestContextHolder获取ServletRequestAttributes,获取HttpServletRequest得到Header中的token的。
如果是网络请求接口,因为网关是同一个服务间token可以通用,就可以调用其他服务。但是如果通过定时任务执行这个方法,因为没有经过Servlet,RequestContextHolder中没有这个线程对应的HttpServletRequest,因此调用OpenFegin的时候就会报[401]unauthorized异常
-------------RequestContextHolder.java
RequestAttributes.getRequestAttributes();
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
--------------ThreadLocal<T>.java
requestAttributesHolder.get();
inheritableRequestAttributesHolder.get();
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
要解决这个问题,首先想到的是既然RequestContextHolder没有当前线程的HttpServletRequest那就添加一个,但是创建一个HttpServletRequest对象过于复杂,也不能保证后续不出问题,所以这个方法不太好。于是就想到了在OpenFegin传递token的时候下手,也就是在OpenFegin的拦截器中处理。如果不能获取到当前线程的HttpServletRequest,那就执行模拟登录,生成一个token,然后将这个token放入到RequestTemplate中,这样就能实现远程的认证调用。
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
if (!support(requestTemplate)) {
return;
}
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = null;
if (attributes != null) {
request = attributes.getRequest();
//添加token 不传递
requestTemplate.header(HttpHeaders.AUTHORIZATION, request.getHeader(HttpHeaders.AUTHORIZATION));
} else {
//业务代码,模拟登录获取token
//添加token
requestTemplate.header(HttpHeaders.AUTHORIZATION, token);
}
}
public boolean support(RequestTemplate requestTemplate) {
//获取openfegin接口是否标准不需要token的注解
MethodMetadata methodMetadata = requestTemplate.methodMetadata();
Method method = methodMetadata.method();
NotTransmitToken[] annotations = method.getDeclaredAnnotationsByType(NotTransmitToken.class);
return !(annotations.length > 0);
}
}
由于这里登录是写死的,所以需要一个特殊的用户,用户名和密码得固定住,或者也可以生成一个永不过期的token。这两种方法都有一定安全风险,需要进一步加密或者优化来保证权限不泄露。