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即可。