服務對外提供介面以供不同站點之間使用:Spring Cloud Feign使用記錄及攜帶token請求
在開發 Spring Cloud 微服務的時候,我們知道,服務之間都是以 HTTP 介面的形式對外提供服務的,因此消費者在進行呼叫的時候,底層就是通過 HTTP Client 的這種方式進行訪問。當然我們可以使用JDK原生的 URLConnection、Apache 的 HTTP Client、Netty 非同步 Http Client,Spring 的 RestTemplate 去實現服務間的呼叫。但是最方便、最優雅的方式是通過 Spring Cloud Open Feign 進行服務間的呼叫。Spring Cloud 對 Feign 進行了增強,使 Feign 支援 Spring Mvc 的註解,並整合了 Ribbon 等,從而讓 Feign 的使用更加方便。
Feign是一個宣告式的Web服務客戶端,使用Feign可使得Web服務客戶端的寫入更加方便。 它具有可插拔註釋支援,包括Feign註解和JAX-RS註解、Feign還支援可插拔編碼器和解碼器、Spring Cloud增加了對Spring MVC註釋的支援,並HttpMessageConverters在Spring Web中使用了預設使用的相同方式。Spring Cloud集成了Ribbon和Eureka,在使用Feign時提供負載平衡的http客戶端。
Feign作用:可以解決不同伺服器介面之間的相互呼叫,即跨域請求!feign結合eureka註冊中心,把不同的服務專案註冊到eureka中,通過feign客戶端進行呼叫,可以解決負載均衡問題。
接下來就簡單講述一下Feign的入門使用
一、引入依賴及配置編寫
1、引入依賴
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>10.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1 .1.RELEASE</version>
</dependency>
2、編寫配置檔案
@Configuration
public class FeignConfiguration {
@Bean
public Contract feignContract() {
return new Contract.Default();
}
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
Decoder feignDecoder() {
return new GsonDecoder();
}
@Bean
Encoder feignEncoder() {
return new GsonEncoder();
}
}
3、在應用主類Application裡,通過@EnableFeignClients註解開啟Spring Cloud Feign的支援功能。
// 在啟動類上加上註解
// 開啟 Feign 掃描支援
@EnableFeignClients
二、編寫Feign介面及使用
1、編寫Feign介面
@FeignClient註解指定服務名來繫結服務,然後再使用Spring MVC的註解來繫結具體該服務提供的REST介面。
@FeignClient(name = "myApi", url = "http://localhost:8080")
public interface MyService {
// 呼叫另外一個服務的介面
@RequestLine("GET /getUsers?searchString={searchString}")
@Headers("Content-Type: application/json")
List<User> getUsers(@Param("searchString") String searchString);
}
2、使用介面
Feign介面不需要實現類,可直接呼叫
private MyService myService;
@GetMapping("/userList")
public List<User> getUsers(@RequestParam String searchString){
List<User> userList = myService.getUsers(searchString);
return userList;
}
三、攜帶token請求
為了安全考慮要訪問的服務的介面需要token驗證才能訪問,因此需要攜帶token才能訪問。關於新的服務搭建安全框架,使用與要訪問的平臺一致的token生成和驗證機制,這裡就不贅述了。
1、方案一:直接在@Headers註解中加token
這種方案可以用來測試,因為,這種方式token是寫死的,不能根據瀏覽器攜帶的token進行驗證。
@FeignClient(name = "myApi", url = "http://localhost:8080")
public interface MyService {
@RequestLine("GET /getUsers?searchString={searchString}")
// 直接在@Headers註解中加token
@Headers({"Content-Type: application/json", "Authorization: Bearer eyJh..."})
List<User> getUsers(@Param("searchString") String searchString);
}
2、方案二:根據瀏覽器動態獲取token
如何從瀏覽器中拿到token?
可以看到javax.servlet.http包下有個getHeader的方法,可以獲得當前瀏覽器Header中的資訊。
如何將token放到跨域請求中?
在fegin包中的請求攔截器RequestInterceptor有個apply方法,該方法的預設實現如下:
可以看到,預設的Authorization是通過使用者名稱和密碼進行base64加密得到的,跟我們的token生成方式不一樣,所以直接使用預設的是無法驗證通過的,因此,只需實現RequestInterceptor,重寫apply方法即可
解決方案
編寫配置類,實現RequestInterceptor,重寫apply方法,把瀏覽器header拿到的token放進去。
@Slf4j
@Configuration
@AllArgsConstructor
public class NimBusRequestInterceptor implements RequestInterceptor {
private HttpServletRequest req;
private static final String HEADER_STRING = "Authorization";
@Override
public void apply(RequestTemplate requestTemplate) {
// 如果header沒有auth頭,從cookie獲取token
String token = req.getHeader(HEADER_STRING);
Cookie[] cookies = req.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if (Objects.equals(cookie.getName(), "token")) {
try {
token = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
log.error(LogUtil.getStack(e));
}
}
}
}
requestTemplate.header(HEADER_STRING, token);
}
}
以上就實現了Feign基本使用與攜帶token請求。