教你Spring Cloud保證各個(gè)微服務(wù)之間調(diào)用安全性
導(dǎo)讀:在微服務(wù)的架構(gòu)下,系統(tǒng)會(huì)根據(jù)業(yè)務(wù)拆分為多個(gè)服務(wù),各自負(fù)責(zé)單一的職責(zé),在這樣的架構(gòu)下,我們需要確保各api的安全性,也就是說(shuō)服務(wù)不是開(kāi)放的,而是需要授權(quán)才可訪問(wèn)的,避免接口被不合法的請(qǐng)求所訪問(wèn)。
但是在在微服務(wù)集群中服務(wù)之間暴力的接口,或者對(duì)于第三方開(kāi)放的接口如果不做及安全和認(rèn)證,后果可想而知。
閱讀下文之前思考幾個(gè)問(wèn)題:
- 如何在restTemplate遠(yuǎn)程調(diào)用請(qǐng)求增加添加統(tǒng)一認(rèn)證?
- 服務(wù)認(rèn)證如何規(guī)范加密和解密?
- 遠(yuǎn)程調(diào)用統(tǒng)一什么協(xié)議比較合適?
如下圖,三個(gè)服務(wù)注冊(cè)到同一個(gè)注冊(cè)中心集群,服務(wù)A、B、C之間如果不做任何限制,服務(wù)之間的接口基本是互通的。

但是如果A、B、C之間要做服務(wù)認(rèn)證該如何設(shè)計(jì)?如果外部定制集成服務(wù)D接入怎么保證服務(wù)的安全性?
怎么加認(rèn)證?
假設(shè)服務(wù)A是組織架構(gòu)服務(wù),服務(wù)B是規(guī)則引擎服務(wù),服務(wù)C是公式引擎服務(wù),如果B、C請(qǐng)求A服務(wù)調(diào)用用戶(hù)信息除了開(kāi)放接口規(guī)定參數(shù)。我們?nèi)绾渭尤霗?quán)限認(rèn)證信息。
目前市面上主要兩種方案處理
- 請(qǐng)求體Body中加入?yún)?shù)校驗(yàn) => SDK集成場(chǎng)景較多
- 請(qǐng)求頭Header中加入認(rèn)證信息 token
請(qǐng)求頭Header中加入認(rèn)證信息 token,如下圖結(jié)構(gòu)所示。

確定服務(wù)認(rèn)證統(tǒng)一在request header 加X(jué)-SERVICE-NAME,在服務(wù)服務(wù)中我們不可能每個(gè)服務(wù)都會(huì)主動(dòng)去管理基礎(chǔ)認(rèn)證信息,仔細(xì)閱讀RestTemplate源碼不難發(fā)現(xiàn),RestTemplate實(shí)現(xiàn)接口InterceptingHttpAccessor,因此我們可以再定義自己的攔截器來(lái)統(tǒng)一處理。

攔截@FeignClient 標(biāo)識(shí)攔截
feign遠(yuǎn)程調(diào)用請(qǐng)求增加頭部信息處理,重新定義 RequestInterceptor
public class FeignApiInterceptor implements RequestInterceptor {
/**
* 統(tǒng)一處理feign的遠(yuǎn)程調(diào)用攔截
*/
@Override
public void apply(RequestTemplate requestTemplate) {
// 遠(yuǎn)程調(diào)用請(qǐng)求增加頭部信息處理(簡(jiǎn)寫(xiě)代碼如下)
requestTemplate.header("X-SERVICE-NAME", "...");
}
}
攔截RestTemplate遠(yuǎn)程調(diào)用請(qǐng)求
RestTemplate 提供高度封裝的接口,可以讓我們非常方便地進(jìn)行 Rest API 調(diào)用。常見(jiàn)的方法如下:

RestTemplate遠(yuǎn)程調(diào)用請(qǐng)求增加頭部信息處理,統(tǒng)一處理處理restTemplate的請(qǐng)求攔截。
/**
* restTemplate遠(yuǎn)程調(diào)用請(qǐng)求增加頭部信息處理
*/
@Slf4j
public class RestApiHeaderInterceptor implements ClientHttpRequestInterceptor {
/**
* 處理restTemplate的請(qǐng)求攔截
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
long startTime = System.currentTimeMillis();
try {
HttpHeaders headers = request.getHeaders();
// 遠(yuǎn)程調(diào)用請(qǐng)求增加頭部信息處理(簡(jiǎn)寫(xiě)代碼如下)
requestTemplate.header("X-SERVICE-NAME", "...");
ClientHttpResponse response = execution.execute(request, body);
// 加入鏈路深度Deep
// ....
return response;
} finally {
// do something
}
}
}
服務(wù)注冊(cè)客戶(hù)端配置
@Configuration
@EnableFeignClients(basePackages = NamingConstant.BASE_PACKAGE)
public class DiscoveryClientConfig {
//......
/**
* feign請(qǐng)求攔截器
*/
@Bean
public RequestInterceptor feignInterceptor() {
return new FeignApiInterceptor();
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(HttpClient httpClient,
Environment environment) {
RestTemplate restTemplate = new RestTemplate();
restTemplateBuilder.configure(restTemplate);
restTemplate.setRequestFactory(buildFactory(httpClient, environment));
// 加入自定義攔截器
restTemplate.getInterceptors().add(new RestApiHeaderInterceptor());
return restTemplate;
}
}
拓展補(bǔ)充:
在處理RestTemplate的請(qǐng)求攔截的時(shí)候我們也可以追加鏈路追蹤日志,具體對(duì)于鏈路日志的拓展可查閱《微服務(wù)分布式架構(gòu)中,如何實(shí)現(xiàn)日志鏈路跟蹤?》
簡(jiǎn)寫(xiě)代碼
@Slf4j
public class RestApiHeaderInterceptor implements ClientHttpRequestInterceptor {
/**
* 處理restTemplate的請(qǐng)求攔截
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
long startTime = System.currentTimeMillis();
try {
// 遠(yuǎn)程調(diào)用請(qǐng)求增加頭部信息處理
ClientHttpResponse response = execution.execute(request, body);
// 獲取傳遞線(xiàn)程變量
Collection<String> values = response.getHeaders()
.get(TraceUtil.TRACE_RPCCOUNT);
if (!CollectionUtils.isEmpty(values)) {
int value = Integer.valueOf(values.iterator().next());
// 鏈路深度追加
TraceUtil.getRpcCounter().addAndGet(value + 1);
}
return response;
} finally {
// 是否開(kāi)啟鏈路追蹤
if (TraceUtil.isTraceLoggerOn()) {
// 輸出鏈路日志(接口耗時(shí))
TraceUtil.log(StringHelper.join("dt:", System.currentTimeMillis() - startTime,
", TRACE-RPC-", request.getMethod(),
", URI:", request.getURI()));
}
}
}
}
怎么接收認(rèn)證信息?
讀過(guò)Spring-Web源碼應(yīng)該知道OncePerRequestFilter過(guò)濾器基類(lèi),旨在保證在任何 servlet 容器上每個(gè)請(qǐng)求分派一次執(zhí)行。 它提供了一個(gè)帶有 HttpServletRequest 和 HttpServletResponse 參數(shù)的doFilterInternal方法,詳細(xì)用法可閱讀源碼。
另一種也出現(xiàn)在它自己的線(xiàn)程中的調(diào)度類(lèi)型是ERROR 。 如果子類(lèi)希望靜態(tài)聲明是否應(yīng)該在錯(cuò)誤調(diào)度期間調(diào)用一次,它們可以覆蓋shouldNotFilterErrorDispatch() 。
getAlreadyFilteredAttributeName方法確定如何識(shí)別請(qǐng)求已被過(guò)濾。 默認(rèn)實(shí)現(xiàn)基于具體過(guò)濾器實(shí)例的配置名稱(chēng)。
定義基礎(chǔ)過(guò)濾器
public abstract class BaseWebFilter extends OncePerRequestFilter {
/**
* 返回類(lèi)名,避免filter不被執(zhí)行
*/
@Override
protected String getFilterName() {
return null;
}
}
過(guò)濾器定義虛擬類(lèi), 繼承自接口的過(guò)濾器。如果聲明為springbean,他自動(dòng)加載到請(qǐng)求過(guò)濾鏈(因?yàn)槔^承了GenericFilterBean接口,spring默認(rèn)把繼承此類(lèi)的過(guò)濾器bean加到web過(guò)濾鏈)中,通過(guò)注解@order定義其優(yōu)先級(jí)如果不申明為springbean,又要加入到過(guò)濾鏈中,可以通過(guò)FilterRegistrationBean定義,并指定優(yōu)先級(jí)
封裝攔截 ApiValidationFilter
@Override
public BaseWebFilter getFilterInstance() {
return new BaseWebFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 攔截校驗(yàn)
if (validateToken(request)) {
// 校驗(yàn)通過(guò)執(zhí)行下一個(gè)lan'j
chain.doFilter(request, response);
} else {
// 封裝統(tǒng)一認(rèn)證失敗返回信息
response.setStatus(401);
response.setCharacterEncoding(
Charset.defaultCharset().displayName());
response.setContentType(
MimeTypeUtils.APPLICATION_JSON.toString());
response.getWriter().println(JsonUtil.toJsonString(Response
.err("status.401")));
}
}
};
}
針對(duì)與validateToken方法這里就不做展開(kāi),對(duì)于加密方式大同小異,根據(jù)自己項(xiàng)目情況來(lái)定。
private boolean validateToken(HttpServletRequest request) {
if (!needValidate(request)) {
return true;
}
String security = resolveToken(request);
if (security == null) {
log.info("驗(yàn)證信息缺失,請(qǐng)求地址:{}", request.getRequestURI());
return false;
}
try {
// 解析security加密規(guī)則
return true;
} catch (Exception e) {
log.info("驗(yàn)證信息無(wú)效:{},請(qǐng)求地址:{}", security, request.getRequestURI());
return false;
}
}
總結(jié)
回顧整個(gè)方案設(shè)計(jì)與實(shí)現(xiàn),大致可分為以下幾個(gè)步驟
參數(shù)加入位置:請(qǐng)求頭Header還是請(qǐng)求體Body確定加入范圍:@FeignClient客戶(hù)端、構(gòu)建RestTemplate以及集成服務(wù)遠(yuǎn)程接口調(diào)用確定攔截位置:Web線(xiàn)程攔截器,用于統(tǒng)一處理線(xiàn)程變量,該過(guò)濾器執(zhí)行順序早于springsecurity的過(guò)濾器確定加密/解密方式:加密字符串和解密
到此這篇關(guān)于教你Spring Cloud保證各個(gè)微服務(wù)之間調(diào)用安全性的文章就介紹到這了,更多相關(guān)Spring Cloud微服務(wù)調(diào)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot控制反轉(zhuǎn)與Bean對(duì)象的方法
文章介紹了Spring Boot中的控制反轉(zhuǎn)(IoC)概念,描述了IoC容器如何管理Bean的生命周期和依賴(lài)關(guān)系,它詳細(xì)講解了Bean的注冊(cè)過(guò)程,包括通過(guò)@ComponentScan和@Bean注解的方式,并討論了Bean的依賴(lài)注入方法,如構(gòu)造器注入、Setter注入和字段注入,感興趣的朋友一起看看吧2025-03-03
一文帶你了解Spring中@Enable開(kāi)頭注解的使用
前面的文章給大家介紹?Spring?的重試機(jī)制的時(shí)候有提到過(guò)?Spring?有很多?@Enable?開(kāi)頭的注解,平時(shí)在使用的時(shí)候也沒(méi)有注意過(guò)為什么會(huì)有這些注解,今天就給大家介紹一下2022-09-09
Springboot接收前端的Json但是為null問(wèn)題
這篇文章主要介紹了Springboot接收前端的Json但是為null問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教<BR>2024-05-05
java在linux系統(tǒng)下開(kāi)機(jī)啟動(dòng)無(wú)法使用sudo命令的原因及解決辦法
每次開(kāi)機(jī)自動(dòng)啟動(dòng)的java進(jìn)程,頁(yè)面上的關(guān)機(jī)按鈕都無(wú)法實(shí)現(xiàn)關(guān)機(jī)功能,但是此時(shí)如果以chb賬號(hào)通過(guò)ssh登錄該服務(wù)器,手動(dòng)殺掉tomcat進(jìn)程,然后再重新啟動(dòng)tomcat,頁(yè)面上的關(guān)機(jī)按鈕就有效了2013-08-08
springboot運(yùn)行到dokcer中 dockerfile的場(chǎng)景分析
這篇文章主要介紹了springboot運(yùn)行到dokcer中 dockerfile,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
Springmvc ViewResolver設(shè)計(jì)實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Springmvc ViewResolver設(shè)計(jì)實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Spring Boot jpa Service層代碼實(shí)例
這篇文章主要介紹了Spring Boot jpa Service層代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
Nacos框架服務(wù)注冊(cè)實(shí)現(xiàn)流程
這篇文章主要介紹了SpringCloud服務(wù)注冊(cè)之nacos實(shí)現(xiàn)過(guò)程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08

