SpringCloud Alibaba微服務(wù)實(shí)戰(zhàn)之隱私接口禁止外部訪問
本文轉(zhuǎn)載自微信公眾號「JAVA日知錄」,作者飄渺Jam。轉(zhuǎn)載本文請聯(lián)系JAVA日知錄公眾號。
大家好,我是飄渺!
在SpringCloud實(shí)戰(zhàn)系列文章中曾經(jīng)介紹過在SpringCloud體系下如何防止前端請求繞過網(wǎng)關(guān)直接到達(dá)后端微服務(wù),今天我們要反其道而行之,介紹在SpringCloud體系中如何防止內(nèi)部隱私接口被網(wǎng)關(guān)調(diào)用。
看到這里可能有的同學(xué)會(huì)有點(diǎn)暈,怎么還有這種業(yè)務(wù)場景呢,別急,咱們先回顧一下我們的業(yè)務(wù)場景。
業(yè)務(wù)場景
客戶端通過網(wǎng)關(guān)調(diào)用OrderService服務(wù)獲取數(shù)據(jù),OrderService通過Feign調(diào)用AccountService服務(wù),而當(dāng)AccountService提供對應(yīng)的Feign接口后,客戶端是可以通過網(wǎng)關(guān)直接調(diào)用AccountService接口的。
現(xiàn)在假設(shè)AccountService提供的接口包含了部分隱私數(shù)據(jù),只允許內(nèi)部調(diào)用協(xié)助OrderService進(jìn)行業(yè)務(wù)邏輯處理,不允許客戶端直接獲取,此時(shí)咱們需要怎么做?
業(yè)務(wù)實(shí)戰(zhàn)
我們先通過代碼將原始的流程實(shí)現(xiàn)出來,即通過網(wǎng)關(guān)調(diào)用OrderService的OrderController,然后在OrderController中通過Feign調(diào)用AccountService的AccountController,為了便于閱讀,文章中刪除了部分無用代碼。
模擬實(shí)現(xiàn)
入口 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調(diào)用AccountService
- ResultData<String> secretValue = accountClient.getSecretValue();
Feign接口
- public interface AccountApi {
- ...
- @GetMapping("/account/getSecretValue")
- ResultData<String> getSecretValue();
- ...
- }
AccountController實(shí)現(xiàn)
- @RestController
- @Log4j2
- @Api(tags = "account接口")
- @RequiredArgsConstructor(onConstructor = @__(@Autowired))
- public class AccountController implements AccountApi {
- /**
- * 隱私接口,禁止通過網(wǎng)關(guān)訪問
- */
- @Override
- @GetMapping("/account/getSecretValue")
- public ResultData<String> getSecretValue() {
- return ResultData.success("隱私接口,禁止通過網(wǎng)關(guān)訪問");
- }
- }
正如我們前面所說,一旦提供了Feign接口,在默認(rèn)情況下我們可以直接通過網(wǎng)關(guān)訪問getSecretValue()方法,那怎么確保這個(gè)方法不讓外部調(diào)用呢?
解決方案
網(wǎng)上現(xiàn)在大部分的解決辦法是基于黑名單機(jī)制,即將這些接口放入“黑名單”中存儲(chǔ)起來,在網(wǎng)關(guān)啟動(dòng)時(shí)讀取黑名單配置,然后校驗(yàn)是否在黑名單中。
這種辦法確實(shí)也可以,但是總感覺不夠靈活,而且實(shí)現(xiàn)也比較繁瑣,這里就不展開了。
我們今天介紹的是利用訪問路徑來實(shí)現(xiàn),非常簡單輕便。
實(shí)現(xiàn)原理
我們需要借助接口路徑規(guī)范來實(shí)現(xiàn),即給接口指定訪問路徑時(shí)采用這樣的格式 : /訪問控制/接口。
訪問控制可以有以下幾個(gè)規(guī)則(參考JAVA包規(guī)范),可根據(jù)業(yè)務(wù)需要進(jìn)行擴(kuò)展。
- pb - public 所有請求均可訪問
- pt - protected 需要進(jìn)行token認(rèn)證通過后方可訪問
- pv - private 無法通過網(wǎng)關(guān)訪問,只能微服務(wù)內(nèi)部調(diào)用
- df - default 網(wǎng)關(guān)請求token認(rèn)證,并且請求參數(shù)和返回結(jié)果進(jìn)行加解密
- ...
有了這套接口規(guī)范以后,我們就可以靈活控制接口訪問權(quán)限,然后在網(wǎng)關(guān)對接口路徑進(jìn)行校驗(yàn),如果命中對應(yīng)的訪問控制規(guī)則就進(jìn)行對應(yīng)的邏輯處理。
代碼實(shí)戰(zhàn)
既然知道了實(shí)現(xiàn)原理,那寫代碼就很簡單了。
修改接口訪問路徑,遵循接口路徑規(guī)范
- public interface AccountApi {
- @GetMapping("/pv/account/getSecretValue")
- ResultData<String> getSecretValue();
- }
修改feign的訪問路徑。
- @RestController
- @Log4j2
- @Api(tags = "account接口")
- @RequiredArgsConstructor(onConstructor = @__(@Autowired))
- public class AccountController implements AccountApi {
- /**
- * 隱私接口,禁止通過網(wǎng)關(guān)訪問
- */
- @Override
- @GetMapping("/pv/account/getSecretValue")
- public ResultData<String> getSecretValue() {
- return ResultData.success("隱私接口,禁止通過網(wǎng)關(guān)訪問");
- }
- }
修改接口實(shí)現(xiàn)類的訪問路徑,這里需要與Feign的路徑保持一致。
網(wǎng)關(guān)自定義攔截器進(jìn)行接口校驗(yàn)
- @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);
- }
- /**
- * 判斷是否內(nèi)部私有方法
- * @param requestURI 請求路徑
- * @return boolean
- */
- private boolean isPv(String requestURI) {
- return isAccess(requestURI,"/pv");
- }
- /**
- * 網(wǎng)關(guān)訪問控制校驗(yàn)
- */
- private boolean isAccess(String requestURI, String access) {
- //后端標(biāo)準(zhǔn)請求路徑為 /訪問控制/請求路徑
- int index = requestURI.indexOf(access);
- return index >= 0 && StringUtils.countOccurrencesOf(requestURI.substring(0,index),"/") < 1;
- }
- }
通過上面簡單兩步我們就能實(shí)現(xiàn)本文提出的問題了,接下來我們測試一下。
測試
直接訪問后端服務(wù),提示無法訪問。
通過OrderService訪問后端服務(wù)正常訪問。
小結(jié)
讓內(nèi)部隱私接口不被外部訪問,我相信做微服務(wù)開發(fā)的同學(xué)基本都會(huì)遇到。本文中提供的解決方案代碼量很少而且接口路徑規(guī)范可以根據(jù)自己的業(yè)務(wù)規(guī)則進(jìn)行修改擴(kuò)展,推薦大家使用。其實(shí)代碼不是關(guān)鍵,關(guān)鍵在于要讓團(tuán)隊(duì)共同遵守這個(gè)接口規(guī)范,思想比實(shí)現(xiàn)更重要。