Feign客户端请求远程服务接口时,需要携带token进行认证(详见《微服务迁移记(六):集成jwt保护微服务接口安全》),token有超时时间设置,当超时后,需要重新刷新token。如果每个接口都去判断,那就费事了,最好的办法是在拦截器里做。我这里使用的是OkHttp,新增一个OkHttpInterceptor的拦截器:
@Slf4j public class OkHttpInterceptor implements HandlerInterceptor,Interceptor { @Autowired private ApiInitService apiInitService; @Autowired private RedisUtil redisUtil; @Value("${app_id}") String app_id; @Value("${app_secret}") String app_secret; @Override public Response intercept(Chain chain) throws IOException { log.info("进入okhttp拦截器"); Request request = chain.request(); try { Response response = chain.proceed(request); ResponseBody responseBody = response.body(); BufferedSource source = responseBody.source(); source.request(Long.MAX_VALUE); Buffer buffer = source.getBuffer(); MediaType mediaType = responseBody.contentType(); if(isPlaintext(buffer)){ Charset charset = Charset.forName("UTF-8"); String result = buffer.clone().readString(mediaType.charset(charset)); log.info("result:"+result); //如果token超时或不存在,则重新获取 ResponseData responseData = JSONObject.parseObject(result,ResponseData.class); if(responseData.getCode() == CodeEnum.UNKNOWNTOKEN.getCode()){ //重新获取token String tokenData = this.apiInitService.getToken(app_id,app_secret); if(StringUtils.isNotEmpty(tokenData)){ //重新将token存到redis中 redisUtil.set("Authorization",tokenData); } } } return response; }catch (Exception e){ throw e; } } /** * Returns true if the body in question probably contains human readable text. Uses a small sample * of code points to detect unicode control characters commonly used in binary file signatures. */ static boolean isPlaintext(Buffer buffer) throws EOFException { try { Buffer prefix = new Buffer(); long byteCount = buffer.size() < 64 ? buffer.size() : 64; buffer.copyTo(prefix, 0, byteCount); for (int i = 0; i < 16; i++) { if (prefix.exhausted()) { break; } int codePoint = prefix.readUtf8CodePoint(); if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { return false; } } return true; } catch (EOFException e) { return false; // Truncated UTF-8 sequence. } } private boolean bodyEncoded(Headers headers) { String contentEncoding = headers.get("Content-Encoding"); return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); } }
注意,这里不需要加@Components,因为我在这个控制器里有注入Service和RedisUtil以及配置项,如果直接扫包方式载入,会造成注入对象为null的错误。具体原因是拦截器是在Springcontext之前加载。
新增一个Okhttp的配置类:
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) @Slf4j public class OkHttpClientConfig { private OkHttpClient okHttpClient; @Bean public OkHttpInterceptor okHttpInterceptor(){ return new OkHttpInterceptor(); } @Bean public okhttp3.OkHttpClient okHttpClient(OkHttpClientFactory okHttpClientFactory, FeignHttpClientProperties httpClientProperties) { this.okHttpClient = okHttpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).connectTimeout(httpClientProperties.getConnectionTimeout(),TimeUnit.SECONDS) .followRedirects(httpClientProperties.isFollowRedirects()) .addInterceptor(okHttpInterceptor()) .build(); return this.okHttpClient; } }
配置这个类时,我一开始犯了一个错误,抄了网上一段代码,直接在增加拦截器时使用了:
.addInterceptor(new OkHttpInterceptor())
这种方式和为拦截器加注解一样,并不能提前加载拦截器,所以需要如上面的代码,将拦截器独立出来一个bean,将这个bean加入到okhttp即可。