我们基于springcloud的微服务项目,难免会使用feign做接口间的数据传递,
但是有些微服务不走网关鉴权或者没有网关或者需要单独对请求的服务做一些权限拦截等 等原因,
在这样情况下,前端的用户 token 又不能用,只能做一个服务间的token校验,
保证调用方式是可靠的,大部分文章都是通过获取ServletRequestAttributes中获取token 来设置,
我们不走寻常路,通过注解的方式解决,因为feign是编译器就处理了注解,没办法自定义注解,
但是可以使用feign自己的注解,本文是服务发起方(消费者)的拦截,下篇是被调用方(生产者)的拦截,
暂时没写下篇,感觉比较简单,如果有小伙伴需要的话再考虑出下篇,废话不哔哔,
直接上干货
- pom依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${feign-okhttp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 自定义config
public class CustomMvcContract extends SpringMvcContract {
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
super.processAnnotationOnMethod(data, methodAnnotation, method);
Headers headers = method.getAnnotation(Headers.class);
if (null != headers) {
String[] value = headers.value();
for (String key : value) {
if (key.contains(FeignRequestInterceptor.RPC_SERVER)) {
data.template().header(FeignRequestInterceptor.RPC_SERVER, FeignRequestInterceptor.getRpcServerCaller());
}
}
}
}
}
@Slf4j
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignConfig {
@Autowired
private OkHttpLoggingInterceptor okHttpLoggingInterceptor;
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
public Contract contract() {
return new CustomMvcContract();
}
@Bean
public okhttp3.OkHttpClient okHttpClient() {
return new okhttp3.OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool())
.addInterceptor(okHttpLoggingInterceptor)
.build();
}
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
@Bean
public Decoder feignDecoder() {
return new ResultStatusDecoder(new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))));
}
}
import cn.hutool.core.util.StrUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Map;
@Slf4j
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
public static final String RPC_SERVER = "RPC_CALL";
public static final String RPC_SERVER_HEADER_KEY = "AUTH_RPC_KEY";
@Getter
private static String httpKey;
@Getter
private static String rpcServerCaller;
public static String getFeignAuthToken() throws Exception {
return httpKey + ":" + rpcServerCaller + ":" + System.currentTimeMillis();
}
@Value("${spring.application.name}")
private void setApplicationName(String applicationName) {
FeignRequestInterceptor.rpcServerCaller = applicationName.toUpperCase();
}
@Value("${http.key:123232}")
private void setHttpKey(String httpKey) {
FeignRequestInterceptor.httpKey = httpKey;
}
@Override
public void apply(RequestTemplate requestTemplate) {
try {
Map<String, Collection<String>> headers = requestTemplate.headers();
if (headers.containsKey(RPC_SERVER)) {
requestTemplate.header(RPC_SERVER_HEADER_KEY, getFeignAuthToken());
}
} catch (Exception e) {
log.error("动态添加http rpc server header失败:{}", e);
}
}
}
import feign.Response;
import feign.codec.Decoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
@Slf4j
public final class ResultStatusDecoder implements Decoder {
final Decoder delegate;
public ResultStatusDecoder(Decoder delegate) {
Objects.requireNonNull(delegate, "解码器不能为空. ");
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type) throws IOException {
String resultStr = IOUtils.toString(response.body().asInputStream(), String.valueOf(StandardCharsets.UTF_8));
return delegate.decode(response.toBuilder().body(resultStr, StandardCharsets.UTF_8).build(), type);
}
}
import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class OkHttpLoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
try {
long start = System.nanoTime();
if (log.isInfoEnabled()) {
log.info("feign发送请求 【{}】 on method: 【{}】 ,headers【{}】",
request.url(), request.method(), request.headers());
}
Response response = chain.proceed(request);
if (log.isInfoEnabled()) {
long end = System.nanoTime();
ResponseBody responseBody = response.peekBody(1024 * 1024);
String respStr = responseBody.string();
log.info(String.format("feign响应: [%s] %n返回json:【%s】 %.1fms%n%s",
response.request().url(),
respStr,
(end - start) / 1e6d,
response.headers()));
}
return response;
} catch (Exception e) {
throw e;
} finally {
}
}
}
- 写个简单小样例测试一下:
import com.config.FeignRequestInterceptor;
import feign.Headers;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "BaiDuFeignClient", url = "http://www.baidu.com", path = "/",fallback = BaiDuFeignClientFallBack.class)
public interface BaiDuFeignClient {
@Headers(value = FeignRequestInterceptor.RPC_SERVER)
@RequestMapping("/s")
String searchWord(@RequestParam("wd") String word);
}
import org.springframework.stereotype.Component;
@Component
public class BaiDuFeignClientFallBack implements BaiDuFeignClient {
@Override
public String searchWord(String word) {
return "请求出错";
}
}
import com.consumer.BaiDuFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/search")
public class SearchController {
@Autowired
private BaiDuFeignClient baiDuFeignClient;
@GetMapping("/searchWord/{word}")
public String searchWord(@PathVariable(value = "word", required = false) String word) {
log.info("待搜索的词语:{}",word);
return baiDuFeignClient.searchWord(word);
}
}
- 效果如图:

- 这是我的微信公众号二维码,感谢关注,我会持续分享
