微服務開發時,接口不能對外暴露怎么辦?
在業務開發的時候,經常會遇到某一個接口不能對外暴露,只能內網服務間調用的實際需求。面對這樣的情況,我們該如何實現呢?
今天,我就來說一下碼猿慢病云管理系統中是如何實現的?
常用方案
在介紹碼猿慢病云管理系統中的實現方式,先來介紹常用的兩種方案。
1. 網關+白名單
此方案需要在緩存中維護一套接口白名單,請求到達網關處,先判斷白名單緩存中是否存在,存在則放行,反之則攔截。
網關+白名單
該方案的好處是,對業務代碼零侵入,只需要維護好白名單列表即可;
不足之處在于,白名單的維護是一個持續性投入的工作,在很多公司,業務開發無法直接觸及到 redis,只能提工單申請,增加了開發成本;
另外,每次請求進來,都需要判斷白名單,增加了系統響應耗時,考慮到正常情況下外部進來的請求大部分都是在白名單內的,只有極少數惡意請求才會被白名單機制所攔截,所以該方案的性價比很低。
2. 網關+AOP
相比于方案一對接口進行白名單判斷而言,方案二是對請求來源進行判斷,并將該判斷下沉到業務側。避免了網關側的邏輯判斷,從而提升系統響應速度。
我們可以在所有內部的調用請求頭中增加一個header標志這是一個內部請求,比如加個請求頭:from=Y
只要在業務接口處通過AOP的方式判斷一下請求頭中是否含有from=Y,如果有,則是內部請求,反之則是外部請求
網關+AOP
實現
碼猿慢病云管理系統中采用的是第二種方案:網關+AOP ,下面來介紹一下具體的代碼實現。
1. 定義注解
這里AOP在碼猿慢病云管理系統中采用的是注解的方式,注解如下:
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
/**
* 是否AOP統一處理
*/
boolean value() default true;
}
2. 網關處理
在網關處需要對請求頭中的from進行清洗,避免有意之人偽裝內部請求,這里需要做的就是對每個請求直接移除from這個請求頭,直接使用全局過濾器即可完成,代碼如下:
/**
* {@link com.code.ape.codeape.gateway.filter.CodeapeRequestGlobalFilter#filter}
* @author 公眾號:碼猿技術專欄
* @url: www.java-family.cn
*/
public class CodeapeRequestGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 清洗請求頭中from 參數
ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {
httpHeaders.remove(SecurityConstants.FROM);
// 設置請求時間
httpHeaders.put(CommonConstants.REQUEST_START_TIME,
Collections.singletonList(String.valueOf(System.currentTimeMillis())));
}).build();
.......
.......
}
3. feign接口處理
既然是內部調用,按照之前的約定是要在請求頭中添加一個from=Y,因此在feign接口中需要新增這個請求頭,方式很簡單,比如設備feign接口,如下:
/**
* @author 公眾號:碼猿技術專欄
* @url: www.java-family.cn
* @description 設備的feign接口
*/
@FeignClient(contextId = "remoteDeviceService", value = ServiceNameConstants.DEVICE_SERVICE)
public interface RemoteDeviceService {
/**
* 通過Sn查詢
* @param sn 設備SN號
* @return 設備詳細信息
*/
@GetMapping(value = "/device/sn/{sn}",headers = "from=Y")
R<DeviceInfoVO> getBySn(@PathVariable("sn" ) String sn);
}
@GetMapping中的headers屬性即可完成新增請求頭,同樣的比如@RequestMapping、@PostMapping等也是支持的。
這樣的話在feign接口發出請求時則會自動在請求頭中新增from=Y了。
4. AOP處理
在第1步中定義了@Inner這個注解,標注在controller方法上表示這個接口只允許內部調用,代碼如下:
圖片
@IngoreAuth這個注解是繞過鑒權的作用,前面文章中也有分享。
那么這個注解內部的實現原理是什么呢?代碼如下:
//com.code.ape.codeape.common.security.component.CodeapeSecurityInnerAspect
@Slf4j
@Aspect
@RequiredArgsConstructor
public class CodeapeSecurityInnerAspect implements Ordered {
private final HttpServletRequest request;
@SneakyThrows
@Around("@within(inner) || @annotation(inner)")
public Object around(ProceedingJoinPoint point, Inner inner) {
//取出請求頭中的from屬性
String header = request.getHeader("from");
//判斷from===Y
if (inner.value() && !"Y".equals(header)) {
//不符合規則,直接拋出異常,返回給客戶端無權限
log.warn("訪問接口 {} 沒有權限", point.getSignature().getName());
throw new AccessDeniedException("Access is denied");
}
return point.proceed();
}
.......
}
如果請求頭中的from屬性不匹配,則拋出AccessDeniedException異常,會被全局異常捕獲,返回403的狀態碼,代碼如下:
圖片
總結
本節內容介紹了微服務中接口不對外暴露的兩種方案:
- 網關+白名單
- 網關+AOP
當然還有其他的實現方式,生產中根據項目需要選擇合適的方案為最佳。