【Dubbo3高级特性】「提升系统安全性」通过令牌进行服务验证及服务鉴权控制实战指南
系列文章目錄
如果你看到了這里,那么接下來你將會認識Dubbo3的誕生將如何引領微服務領域更進一步,從而邁入云原生的領域,這當然不僅僅是Dubbo3,之前也介紹了Java生態另外一個云原生領域的技術Quarkus等技術,而本文內容側重點去介紹Dubbo3邁向云原生
的技術分析和探索,如果有不正確的地方,還需要大家多多指正。
文章目錄
- 系列文章目錄
- 通過令牌進行服務驗證
- 使用場景
- 主要原理
- 運作流程和實現原理
- 服務提供者
- 注冊中心
- 服務消費者
- 服務提供者
- 配置方式(服務提供者)
- 服務級別
- 隨機token令牌,使用UUID生成
- xml配置模式進行控制
- SpringBoot的配置模式進行控制
- 固定token令牌,相當于密碼
- xml配置模式進行控制
- SpringBoot的配置模式進行控制
- 接口類級別
- 隨機token令牌,使用UUID生成
- 固定token令牌,相當于密碼
- 配置方式(服務消費者)
- 注意要點
- 實際案例
- 建立API接口
- 建立服務端的案例
- 建立消費端的案例
- 注冊中心模式進行調用
- 直連模式進行調用
- 出現了報錯!
- consumer incorrect token is null
- 建立服務端的案例2
- 直連模式進行調用
- 通過服務鑒權控制調用
- 服務鑒權-特性說明
- 鑒權服務中心
- 總體流程圖
- 使用場景
- 使用方式
- 接入方式
- 實現案例
- 定義鑒權服務中心服務
- 建立Dubbo3的服務容器作為鑒權服務實現
- 數據模型
- 定義對應的實現鑒權中心鑒權匹配實現類
- 定義Hutools的數據源DB
- 實現查詢數據庫的Hutool操作
- 實現傳遞過來的數據庫的Hutool操作
- 定義容器的dubbo.properties
- 服務提供端
- 服務提供端-建立服務鑒權過濾器
- 建立dubbo.properties配置信息讀取相關的鑒權服務的地址
- 服務消費端
- 模擬遠程調用
通過令牌進行服務驗證
令牌Token的驗證方式主要通過客戶端和服務端的令牌驗證定義并且通過注冊中心層面進行維護和存儲和管理的實現機制。
使用場景
在一定程度上實現客戶端和服務端的可信鑒權,避免任意客戶端都可以訪問,降低出現安全問題的風險。
主要原理
通過令牌驗證在注冊中心控制權限,以決定要不要下發令牌給消費者, 可以防止消費者繞過注冊中心訪問提供者, 另外通過注冊中心可靈活改變授權方式,而不需修改或升級提供者。如下圖所示。
運作流程和實現原理
服務提供者
注冊中心
服務消費者
服務提供者
配置方式(服務提供者)
配置對應的token值的范圍有幾種方式,我們常用的token配置主要有服務級別和接口級別等。
令牌驗證,為空表示不開啟,如果為true,表示隨機生成動態令牌,否則使用靜態令牌,令牌的作用是防止消費者繞過注冊中心直接訪問,保證注冊中心的授權功能有效,如果使用點對點調用,需關閉令牌功能
服務級別
可以設置對應的整個服務應用級別的配置,但是優先級會被接口級別的覆蓋,可以作為全局的默認值所使用。
隨機token令牌,使用UUID生成
該參數接收兩種類型值:boolean類型-True則生成UUID隨機令牌,若為String則自定義令牌。
xml配置模式進行控制
<dubbo:provider token="true" />SpringBoot的配置模式進行控制
使用 Spring Boot 減少非必要配置,結合 Annotation 與 application.properties/application.yml 開發 Dubbo 應用
dubbo.provider.token=true或者
dubbo:provider:token: true使用這種方式的安全級別好一些,因為每次生產的都是uuid,無規律話,不容易被第三方客戶端進行破解從而進行調用。
固定token令牌,相當于密碼
定義了全局provider的token數據uuid模式,對所有的接口和服務實現均起作用!定義了全局provider的token數據-123456,對所有的接口和服務實現均起作用!
xml配置模式進行控制
<dubbo:provider token="123456" />SpringBoot的配置模式進行控制
dubbo.provider.token=123456或者
dubbo:provider:token: 123456接口類級別
隨機token令牌,使用UUID生成
實現方式和效果與服務級別相同。
<dubbo:service interface="com.xxx.TestService" token="true" />或者可以采用@DubboService注解中的token屬性進行標識。
定義了該service接口的token數據,對該接口的所有方法實現均起作用!
固定token令牌,相當于密碼
<dubbo:service interface="com.xxx.TestService" token="123456" />或者可以采用@DubboService注解中的token屬性進行標識。
定義了該service接口的token數據,對該接口的所有方法實現均起作用!
配置方式(服務消費者)
Dubbo官方并未直接暴漏對應的對于消費者端的DubboReference或者ReferenceConfig上配置token,但是通過源碼可以知道,Dubbo采用隱式參數傳遞token,通過attachment進行攜帶進行傳輸。
RpcContext.getContext().setAttachment("token","123456"):注意要點
-
token的配置也可以在,協議級別,使用的spring boot的starter配置中未找到協議級別如何配置。
-
配置Token的生產者的服務,只會允許消費者通過注冊中心注冊后,才可以獲取到對應的token數據,再消費的數據才能夠訪問,否則會出現出現無效token的錯誤。
-
由上面的介紹,數據token是由注冊中心下發拉取到的。
實際案例
建立API接口
public interface CommonRpcApi {RpcResponse<Boolean> tokenAuth(RpcRequest<String> requestParam); }建立服務端的案例
@DubboService(token = "token") public class DefaultCommonRpcApi implements CommonRpcApi {@Overridepublic RpcResponse<Boolean> tokenAuth(RpcRequest<String> requestParam) {return RpcResponse.success();} }建立消費端的案例
注冊中心模式進行調用
@DubboReferenceCommonRpcApi commonRpcApi;@GetMapping("/token")public ResponseEntity<RpcResponse<String>> doTokenAuth(){return ResponseEntity.ok(commonRpcApi.tokenAuth(new RpcRequest(RpcContext.getContext().getAttachment("token"))));}驗證結果沒有任何問題和錯誤異常。
直連模式進行調用
模擬非注冊中心過來的外部rpc調用,用于校驗token不同或者不進行token鑒權的場景!
@DubboReference(url = "dubbo://${dubbo.address:localhost}:28081")CommonRpcApi commonRpcApi;@GetMapping("/token")public ResponseEntity<RpcResponse<String>> doTokenAuth(){return ResponseEntity.ok(commonRpcApi.tokenAuth(new RpcRequest(RpcContext.getContext().getAttachment("token"))));}出現了報錯!
org.apache.dubbo.rpc.RpcException: Invalid token! Forbid invoke remote service interface com.dubbo.shopping.api.oss.CommonRpcApi method tokenAuth() from consumer 192.168.1.104 to provider 192.168.1.104, consumer incorrect token is nullconsumer incorrect token is null
出現了不一致的問題在,當存在這種場景我們很難捕捉到對應的uuid模式的token值,那么我們可以指定token值進行測試效果。
建立服務端的案例2
@DubboService(token = "123456") public class DefaultCommonRpcApi implements CommonRpcApi {@Overridepublic RpcResponse<Boolean> tokenAuth(RpcRequest<String> requestParam) {return RpcResponse.success();} }直連模式進行調用
我們手動注入token進行控制校驗模式
@DubboReference(url = "dubbo://${dubbo.address:localhost}:28081")CommonRpcApi commonRpcApi;@GetMapping("/token")public ResponseEntity<RpcResponse<String>> doTokenAuth(){RpcContext.getContext().setAttachment("token","123456");return ResponseEntity.ok(commonRpcApi.tokenAuth(new RpcRequest(RpcContext.getContext().getAttachment("token"))));}發現調用結果又變的正常了!
通過服務鑒權控制調用
基于上面的【通過令牌進行服務驗證】的控制實現,對于安全性而言還是缺乏了機動性、可配置、靈活性等。所以接下來引入通過了【服務鑒權控制】從而增加安全性和機動性以及可配置化等功能實現。
服務鑒權-特性說明
Dubbo3服務鑒權類似支付之類的對安全性敏感的業務可能會有限制匿名調用的需求。在加固安全性方面,2.7.5引入了基于AK/SK機制的認證鑒權機制,并且引入了鑒權服務中。
鑒權服務中心
主要原理是消費端在請求需要鑒權的服務時,會通過SK、請求元數據、時間戳、參數等信息來生成對應的請求簽名,通過Dubbo3的Attachment機制攜帶到對端進行驗簽,驗簽通過才進行業務邏輯處理。如下圖所示:
總體流程圖
使用場景
針對于調用方進行相關的服務調用鑒權。
使用方式
接入方式
使用者需要在微服務站點上填寫自己的應用信息,并為該應用生成唯一的證書憑證。
在管理站點上提交工單,申請某個敏感業務服務的使用權限,并由對應業務管理者進行審批,審批通過之后,會生成對應的 AK/SK到鑒權服務中心。
導入該證書到對應的應用下,并且進行配置。配置方式也十分簡單,以注解方式為例:
實現案例
定義鑒權服務中心服務
建立Dubbo3的服務容器作為鑒權服務實現
Undertow容器處理功能,用于接收對應的Http鑒權請求接口服務,用于處理來資源服務提供者端的Http請求。
數據模型
定義對應的實現鑒權中心鑒權匹配實現類
定義Hutools的數據源DB
在對應的resources下的config文件下建立db.setting文件,之后配置對應的數據源
#中括表示一個分組,其下面的所有屬性歸屬于這個分組,在此分組名為ds1,也可以沒有分組 [ds1] #自定義數據源設置文件,這個文件會針對當前分組生效,用于給當前分組配置單獨的數據庫連接池參數,沒有則使用全局的配置 driver = com.mysql.jdbc.Driver #JDBC url,必須 url = jdbc:mysql://127.0.0.1:3306/dubbo-shopping #用戶名,必須 user = root #密碼,必須,如果密碼為空,請填寫 pass = pass = root$實現查詢數據庫的Hutool操作
List<Entity> authData = Db.use(dataSource).query("select * from auth_data"); if(CollectionUtil.isNotEmpty(authData)){Entity entity = authData.get(0);String ak = entity.getStr("ak");String sk = entity.getStr("sk"); }實現傳遞過來的數據庫的Hutool操作
@Data @Slf4j @NoArgsConstructor public class AuthService {DataSource dataSource = DSFactory.get("ds1");/*** 匹配ak和sk的值* @param appCode* @param appKey* @param secretKey* @return*/public boolean matchSecretKey(String appCode,String appKey,String secretKey){log.info("appCode:{} - local-appkey:{} - local-secretKey:{}",appCode,appKey,secretKey);if(StringUtils.isEmpty(appKey)){return Boolean.FALSE;}try {List<Entity> authData = Db.use(dataSource).query("select * from auth_data where code = ?",appCode);if(CollectionUtil.isNotEmpty(authData)){Entity entity = authData.get(0);String ak = entity.getStr("ak");String sk = entity.getStr("sk");log.info("remote-appkey:{} - remote-secretKey:{}",ak,sk);if(ak.equals(ak)){if(StringUtils.isEmpty(sk)){return Boolean.FALSE;}else if(!SecureUtil.md5(sk).equals(secretKey)){return Boolean.FALSE;}}else{return Boolean.FALSE;}return Boolean.TRUE;}return Boolean.FALSE;} catch (SQLException e) {log.error("auth is error!",e);return Boolean.FALSE;}}}``` #### 鑒權服務 UndertowContainer采用Undertow容器服務機制,在之前的章節已經介紹和說明了如何實現對應的dubbo的自定義容器實現,在這里我們使用的是UndertowContainer。如果想要學習可以關注之前的文章章節。```java package com.hyts.assemble.dubbo3.comp.container; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.NumberUtil; import com.hyts.assemble.dubbo3.comp.auth.AuthService; import io.undertow.Undertow; import io.undertow.util.Headers; import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.container.Container; import java.nio.file.Files; import java.nio.file.Paths; import java.util.stream.Collectors;public class UnderTowContainer implements Container {//定義容器端口public static final String UNDERTOW_PORT = "dubbo.undertow.port";//定義容器contextPathpublic static final String UNDERTOW_DEFAULT_PATH = "dubbo.undertow.path";private AuthService authService = new AuthService();//設置HttpHandler回調方法final Undertow server = Undertow.builder().addHttpListener(NumberUtil.parseInt(ConfigurationUtils.getProperty(UNDERTOW_PORT)), "localhost").setHandler(exchange -> {if(exchange.getRequestPath().equals(ConfigurationUtils.getProperty(UNDERTOW_DEFAULT_PATH))){String appKey = String.valueOf(exchange.getQueryParameters().get("appKey").poll());String secretKey = String.valueOf(exchange.getQueryParameters().get("secretKey").poll());String appCode = String.valueOf(exchange.getQueryParameters().get("appCode").poll());boolean result = authService.matchSecretKey(appCode,appKey,secretKey);exchange.getResponseSender().send(String.valueOf(result));}}).build();// 定義啟動方法 @Overridepublic void start() {server.start();}// 定義停止方法 @Overridepublic void stop() {server.stop();} }定義容器的dubbo.properties
dubbo.container=spring,jetty,log4j,undertow dubbo.undertow.path=/auth dubbo.undertow.port=8081服務提供端
只需要設置 service.auth 為 true,表示該服務的調用需要鑒權認證通過。param.sign為true表示需要對參數也進行校驗,之前的章節的內容我們已經介紹了對應的如何建立校驗功能的實現機制控制。
// (注解方式) @DubboService(parameters = {"service.auth","true"}) public class AuthServiceImpl implements AuthService { } <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder/><dubbo:application name="direct-consumer"/><dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181" check="false"/><dubbo:protocol name="dubbo" port="20880"/><bean id="rpcAuthSampleApi" class="com.dubbo.shopping.commodity.rpc.auth.AuthServiceImpl"/><dubbo:service id="rpcAuthSampleApiHandler" interface="com.dubbo.shopping.commodity.api.AuthService"ref="rpcAuthSampleApi" version="0.0.0" filter="auth"><dubbo:parameter key="service.auth" value="true"/> </dubbo:service> </beans>服務提供端-建立服務鑒權過濾器
在之前的章節文章中介紹了對應的META-INF/dubbo下建立org.apache.dubbo.rpc.Filter文件,之后進行auth=com.dubbo.shopping.common.auth.filter.AuthFilter,之后會進行定義我們的authFilter過濾器實現類。
建立dubbo.properties配置信息讀取相關的鑒權服務的地址
resources文件下建立dubbo.properties之后添加對應的內容
dubbo.auth.url=http://localhost:8081/auth可以使用對應的配置工具進行獲取配置信息。
ConfigurationUtils.getProperty("dubbo.auth.url")定義對應的服務提供端-建立服務鑒權過濾器
@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {boolean authNeed = Boolean.valueOf(invoker.getUrl().getParameter("service.auth"));String ak = invocation.getAttachment("AK");String sk = invocation.getAttachment("SK");String authUrl = ConfigurationUtils.getProperty("dubbo.auth.url");if(authNeed){log.info("PROCESS AUTH THE INVOKE!APPKEY {} , SECRETKEY {} : authUrl:{}",ak,sk,authUrl);String result = HttpUtil.get(authUrl+"?appCode="+"dubbo-shopping"+"&appKey="+ak+"&secretKey="+sk);if(!Boolean.valueOf(result)){log.error("NOT AUTH THE INVOKE! APPKEY {} , SECRETKEY {}",ak,sk);throw new RpcException("NOT AUTH THE INVOKE!"); }}log.info("PASS AUTH THE INVOKE!APPKEY {} , SECRETKEY {}",ak,sk); // if(paramSign){ // }return invoker.invoke(invocation);}服務消費端
只需要配置好對應的證書等信息即可,之后會自動地在對這些需要認證的接口發起調用前進行簽名操作,通過與鑒權服務的交互,用戶無需在代碼中配置 AK/SK 這些敏感信息,并且在不重啟應用的情況下刷新 AK/SK,達到權限動態下發的目的。
該方案目前已經提交給 Dubbo 開源社區,并且完成了基本框架的合并,除了 AK/SK 的鑒權方式之外,通過 SPI 機制支持用戶可定制化的鑒權認證以及適配公司內部基礎設施的密鑰存儲。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder/><dubbo:application name="direct-consumer"/><dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181" check="false"/><dubbo:provider token="true"/><dubbo:protocol name="dubbo" port="20880"/><dubbo:reference id="authSampleApi" check="false" interface="com.dubbo.shopping.commodity.api.AuthSampleApi" version="*"/> </beans>模擬遠程調用
package com.dubbo.shopping.commodity.controller;import cn.hutool.crypto.SecureUtil; import com.dubbo.shopping.commodity.api.AnnotationConstants; import com.dubbo.shopping.commodity.api.AuthSampleApi; import com.dubbo.shopping.commodity.api.CommodityQueryApi; import com.dubbo.shopping.commodity.entity.BaseInfo; import com.dubbo.shopping.commodity.model.CommodityQueryDTO; import com.dubbo.shopping.model.rpc.RpcRequest; import io.swagger.annotations.ApiOperation; import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.rpc.RpcContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** <p>* 商品主題信息 前端控制器* </p>** @author libo* @since 2022-05-08*/ @RestController @RequestMapping("/commodity/base-info") public class BaseInfoController {@AutowiredAuthSampleApi authSampleApi;@ApiOperation("權限控制調用")@RequestMapping("/auth")public ResponseEntity auth(){RpcContext.getClientAttachment().setAttachment("AK","dubbo3");RpcContext.getClientAttachment().setAttachment("SK", SecureUtil.md5("123456"));return ResponseEntity.ok(authSampleApi.executeAuth("test parameter"));} }既可以實現鑒權服務機制,大家還可以自己進行擴展實現。
總結
以上是生活随笔為你收集整理的【Dubbo3高级特性】「提升系统安全性」通过令牌进行服务验证及服务鉴权控制实战指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对于一个女孩子来说,刚刚接触编程,真的像
- 下一篇: 强大的企业工作平台—今目标