教你Spring Cloud保證各個微服務之間調用安全性
導讀:在微服務的架構下,系統(tǒng)會根據業(yè)務拆分為多個服務,各自負責單一的職責,在這樣的架構下,我們需要確保各api的安全性,也就是說服務不是開放的,而是需要授權才可訪問的,避免接口被不合法的請求所訪問。
但是在在微服務集群中服務之間暴力的接口,或者對于第三方開放的接口如果不做及安全和認證,后果可想而知。
閱讀下文之前思考幾個問題:
- 如何在restTemplate遠程調用請求增加添加統(tǒng)一認證?
- 服務認證如何規(guī)范加密和解密?
- 遠程調用統(tǒng)一什么協(xié)議比較合適?
如下圖,三個服務注冊到同一個注冊中心集群,服務A、B、C之間如果不做任何限制,服務之間的接口基本是互通的。
但是如果A、B、C之間要做服務認證該如何設計?如果外部定制集成服務D接入怎么保證服務的安全性?
怎么加認證?
假設服務A是組織架構服務,服務B是規(guī)則引擎服務,服務C是公式引擎服務,如果B、C請求A服務調用用戶信息除了開放接口規(guī)定參數。我們如何加入權限認證信息。
目前市面上主要兩種方案處理
- 請求體Body中加入參數校驗 => SDK集成場景較多
- 請求頭Header中加入認證信息 token
請求頭Header中加入認證信息 token,如下圖結構所示。
確定服務認證統(tǒng)一在request header 加X-SERVICE-NAME,在服務服務中我們不可能每個服務都會主動去管理基礎認證信息,仔細閱讀RestTemplate源碼不難發(fā)現(xiàn),RestTemplate實現(xiàn)接口InterceptingHttpAccessor,因此我們可以再定義自己的攔截器來統(tǒng)一處理。
攔截@FeignClient 標識攔截
feign遠程調用請求增加頭部信息處理,重新定義 RequestInterceptor
public class FeignApiInterceptor implements RequestInterceptor { /** * 統(tǒng)一處理feign的遠程調用攔截 */ @Override public void apply(RequestTemplate requestTemplate) { // 遠程調用請求增加頭部信息處理(簡寫代碼如下) requestTemplate.header("X-SERVICE-NAME", "..."); } }
攔截RestTemplate遠程調用請求
RestTemplate 提供高度封裝的接口,可以讓我們非常方便地進行 Rest API 調用。常見的方法如下:
RestTemplate遠程調用請求增加頭部信息處理,統(tǒng)一處理處理restTemplate的請求攔截。
/** * restTemplate遠程調用請求增加頭部信息處理 */ @Slf4j public class RestApiHeaderInterceptor implements ClientHttpRequestInterceptor { /** * 處理restTemplate的請求攔截 */ @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { long startTime = System.currentTimeMillis(); try { HttpHeaders headers = request.getHeaders(); // 遠程調用請求增加頭部信息處理(簡寫代碼如下) requestTemplate.header("X-SERVICE-NAME", "..."); ClientHttpResponse response = execution.execute(request, body); // 加入鏈路深度Deep // .... return response; } finally { // do something } } }
服務注冊客戶端配置
@Configuration @EnableFeignClients(basePackages = NamingConstant.BASE_PACKAGE) public class DiscoveryClientConfig { //...... /** * feign請求攔截器 */ @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; } }
拓展補充:
在處理RestTemplate的請求攔截的時候我們也可以追加鏈路追蹤日志,具體對于鏈路日志的拓展可查閱《微服務分布式架構中,如何實現(xiàn)日志鏈路跟蹤?》
簡寫代碼
@Slf4j public class RestApiHeaderInterceptor implements ClientHttpRequestInterceptor { /** * 處理restTemplate的請求攔截 */ @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { long startTime = System.currentTimeMillis(); try { // 遠程調用請求增加頭部信息處理 ClientHttpResponse response = execution.execute(request, body); // 獲取傳遞線程變量 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 { // 是否開啟鏈路追蹤 if (TraceUtil.isTraceLoggerOn()) { // 輸出鏈路日志(接口耗時) TraceUtil.log(StringHelper.join("dt:", System.currentTimeMillis() - startTime, ", TRACE-RPC-", request.getMethod(), ", URI:", request.getURI())); } } } }
怎么接收認證信息?
讀過Spring-Web源碼應該知道OncePerRequestFilter過濾器基類,旨在保證在任何 servlet 容器上每個請求分派一次執(zhí)行。 它提供了一個帶有 HttpServletRequest 和 HttpServletResponse 參數的doFilterInternal方法,詳細用法可閱讀源碼。
另一種也出現(xiàn)在它自己的線程中的調度類型是ERROR 。 如果子類希望靜態(tài)聲明是否應該在錯誤調度期間調用一次,它們可以覆蓋shouldNotFilterErrorDispatch() 。
getAlreadyFilteredAttributeName方法確定如何識別請求已被過濾。 默認實現(xiàn)基于具體過濾器實例的配置名稱。
定義基礎過濾器
public abstract class BaseWebFilter extends OncePerRequestFilter { /** * 返回類名,避免filter不被執(zhí)行 */ @Override protected String getFilterName() { return null; } }
過濾器定義虛擬類, 繼承自接口的過濾器。如果聲明為springbean,他自動加載到請求過濾鏈(因為繼承了GenericFilterBean接口,spring默認把繼承此類的過濾器bean加到web過濾鏈)中,通過注解@order定義其優(yōu)先級如果不申明為springbean,又要加入到過濾鏈中,可以通過FilterRegistrationBean定義,并指定優(yōu)先級
封裝攔截 ApiValidationFilter
@Override public BaseWebFilter getFilterInstance() { return new BaseWebFilter() { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 攔截校驗 if (validateToken(request)) { // 校驗通過執(zhí)行下一個lan'j chain.doFilter(request, response); } else { // 封裝統(tǒng)一認證失敗返回信息 response.setStatus(401); response.setCharacterEncoding( Charset.defaultCharset().displayName()); response.setContentType( MimeTypeUtils.APPLICATION_JSON.toString()); response.getWriter().println(JsonUtil.toJsonString(Response .err("status.401"))); } } }; }
針對與validateToken方法這里就不做展開,對于加密方式大同小異,根據自己項目情況來定。
private boolean validateToken(HttpServletRequest request) { if (!needValidate(request)) { return true; } String security = resolveToken(request); if (security == null) { log.info("驗證信息缺失,請求地址:{}", request.getRequestURI()); return false; } try { // 解析security加密規(guī)則 return true; } catch (Exception e) { log.info("驗證信息無效:{},請求地址:{}", security, request.getRequestURI()); return false; } }
總結
回顧整個方案設計與實現(xiàn),大致可分為以下幾個步驟
參數加入位置:請求頭Header還是請求體Body確定加入范圍:@FeignClient客戶端、構建RestTemplate以及集成服務遠程接口調用確定攔截位置:Web線程攔截器,用于統(tǒng)一處理線程變量,該過濾器執(zhí)行順序早于springsecurity的過濾器確定加密/解密方式:加密字符串和解密
到此這篇關于教你Spring Cloud保證各個微服務之間調用安全性的文章就介紹到這了,更多相關Spring Cloud微服務調用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
手把手帶你分析SpringBoot自動裝配完成了Ribbon哪些核心操作
這篇文章主要介紹了詳解Spring Boot自動裝配Ribbon哪些核心操作的哪些操作,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-08-08Mybatis-plus中的@EnumValue注解使用詳解
這篇文章主要介紹了Mybatis-plus中的@EnumValue注解使用詳解,在PO類中,如果我們直接使用枚舉類型去映射數據庫的對應字段保存時,往往就會因為類型不匹配導致映射失敗,Mybatis-plus提供了一種解決辦法,就是使用@EnumValue注解,需要的朋友可以參考下2024-02-02SSH框架網上商城項目第25戰(zhàn)之使用java email給用戶發(fā)送郵件
這篇文章主要為大家詳細介紹了SSH框架網上商城項目第25戰(zhàn)之使用java email給用戶發(fā)送郵件,感興趣的小伙伴們可以參考一下2016-06-06Java高并發(fā)編程之CAS實現(xiàn)無鎖隊列代碼實例
這篇文章主要介紹了Java高并發(fā)編程之CAS實現(xiàn)無鎖隊列代碼實例,在多線程操作中,我們通常會添加鎖來保證線程的安全,那么這樣勢必會影響程序的性能,那么為了解決這一問題,于是就有了在無鎖操作的情況下依然能夠保證線程的安全,需要的朋友可以參考下2023-12-12