1. 程式人生 > 實用技巧 >服務對外提供介面以供不同站點之間使用:Spring Cloud Feign使用記錄及攜帶token請求

服務對外提供介面以供不同站點之間使用: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請求。