大家好,我是飄渺!
在SpringCloud實戰系列文章中曾經介紹過在SpringCloud體系下如何防止前端請求繞過網關直接到達后端微服務,今天我們要反其道而行之,介紹在SpringCloud體系中如何防止內部隱私接口被網關調用。
看到這里可能有的同學會有點暈,怎么還有這種業務場景呢,別急,咱們先回顧一下我們的業務場景。
業務場景
客戶端通過網關調用OrderService服務獲取數據,OrderService通過Feign調用AccountService服務,而當AccountService提供對應的Feign接口后,客戶端是可以通過網關直接調用AccountService接口的。
現在假設AccountService提供的接口包含了部分隱私數據,只允許內部調用協助OrderService進行業務邏輯處理,不允許客戶端直接獲取,此時咱們需要怎么做?
業務實戰
我們先通過代碼將原始的流程實現出來,即通過網關調用OrderService的OrderController,然后在OrderController中通過Feign調用AccountService的AccountController,為了便于閱讀,文章中刪除了部分無用代碼。
模擬實現
入口 OrderController
- public class OrderController {
- private final OrderService orderService;
- private final AccountClient accountClient;
- @GetMapping("/order/{orderNo}")
- public ResultData<OrderDTO> getById(@PathVariable("orderNo") String orderNo){
- OrderDTO orderDTO = orderService.selectByNo(orderNo);
- ResultData<String> secretValue = accountClient.getSecretValue();
- log.info(secretValue);
- return ResultData.success(orderDTO);
- }
- }
在OrderController中通過AccountClient調用AccountService
- ResultData<String> secretValue = accountClient.getSecretValue();
Feign接口
- public interface AccountApi {
- ...
- @GetMapping("/account/getSecretValue")
- ResultData<String> getSecretValue();
- ...
- }
AccountController實現
- @RestController
- @Log4j2
- @Api(tags = "account接口")
- @RequiredArgsConstructor(onConstructor = @__(@Autowired))
- public class AccountController implements AccountApi {
- /**
- * 隱私接口,禁止通過網關訪問
- */
- @Override
- @GetMapping("/account/getSecretValue")
- public ResultData<String> getSecretValue() {
- return ResultData.success("隱私接口,禁止通過網關訪問");
- }
- }
正如我們前面所說,一旦提供了Feign接口,在默認情況下我們可以直接通過網關訪問getSecretValue()方法,那怎么確保這個方法不讓外部調用呢?
解決方案
網上現在大部分的解決辦法是基于黑名單機制,即將這些接口放入“黑名單”中存儲起來,在網關啟動時讀取黑名單配置,然后校驗是否在黑名單中。
這種辦法確實也可以,但是總感覺不夠靈活,而且實現也比較繁瑣,這里就不展開了。
我們今天介紹的是利用訪問路徑來實現,非常簡單輕便。
實現原理
我們需要借助接口路徑規范來實現,即給接口指定訪問路徑時采用這樣的格式 : /訪問控制/接口。
訪問控制可以有以下幾個規則(參考JAVA包規范),可根據業務需要進行擴展。
- pb - public 所有請求均可訪問
- pt - protected 需要進行token認證通過后方可訪問
- pv - private 無法通過網關訪問,只能微服務內部調用
- df - default 網關請求token認證,并且請求參數和返回結果進行加解密
- ...
有了這套接口規范以后,我們就可以靈活控制接口訪問權限,然后在網關對接口路徑進行校驗,如果命中對應的訪問控制規則就進行對應的邏輯處理。
代碼實戰
既然知道了實現原理,那寫代碼就很簡單了。
修改接口訪問路徑,遵循接口路徑規范
- public interface AccountApi {
- @GetMapping("/pv/account/getSecretValue")
- ResultData<String> getSecretValue();
- }
修改feign的訪問路徑。
- @RestController
- @Log4j2
- @Api(tags = "account接口")
- @RequiredArgsConstructor(onConstructor = @__(@Autowired))
- public class AccountController implements AccountApi {
- /**
- * 隱私接口,禁止通過網關訪問
- */
- @Override
- @GetMapping("/pv/account/getSecretValue")
- public ResultData<String> getSecretValue() {
- return ResultData.success("隱私接口,禁止通過網關訪問");
- }
- }
修改接口實現類的訪問路徑,這里需要與Feign的路徑保持一致。
網關自定義攔截器進行接口校驗
- @Component
- @Order(0)
- @Slf4j
- public class GatewayRequestFilter implements GlobalFilter {
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- //獲取請求路徑
- String rawPath = exchange.getRequest().getURI().getRawPath();
- if(isPv(rawPath)){
- throw new HttpServerErrorException(HttpStatus.FORBIDDEN,"can't access private API");
- }
- return chain.filter(newExchange);
- }
- /**
- * 判斷是否內部私有方法
- * @param requestURI 請求路徑
- * @return boolean
- */
- private boolean isPv(String requestURI) {
- return isAccess(requestURI,"/pv");
- }
- /**
- * 網關訪問控制校驗
- */
- private boolean isAccess(String requestURI, String access) {
- //后端標準請求路徑為 /訪問控制/請求路徑
- int index = requestURI.indexOf(access);
- return index >= 0 && StringUtils.countOccurrencesOf(requestURI.substring(0,index),"/") < 1;
- }
- }
通過上面簡單兩步我們就能實現本文提出的問題了,接下來我們測試一下。
測試
直接訪問后端服務,提示無法訪問。
通過OrderService訪問后端服務正常訪問。
小結
讓內部隱私接口不被外部訪問,我相信做微服務開發的同學基本都會遇到。本文中提供的解決方案代碼量很少而且接口路徑規范可以根據自己的業務規則進行修改擴展,推薦大家使用。其實代碼不是關鍵,關鍵在于要讓團隊共同遵守這個接口規范,思想比實現更重要。
原文鏈接:https://mp.weixin.qq.com/s/cF7BnmZ2QPAfwpP8qGRbmQ