【Dubbo3高级特性】「提升系统安全性」手把手教你如何通过令牌进行Dubbo3服务验证及服务鉴权控制实战指南(一)https://developer.aliyun.com/article/1470989
实现案例
定义鉴权服务中心服务
建立Dubbo3的服务容器作为鉴权服务实现
Undertow容器处理功能,用于接收对应的Http鉴权请求接口服务,用于处理来资源服务提供者端的Http请求。
数据模型
定义对应的实现鉴权中心鉴权匹配实现类
定义Hutools的数据源DB
在对应的resources下的config文件下建立db.setting文件,之后配置对应的数据源
ini
复制代码
#中括表示一个分组,其下面的所有属性归属于这个分组,在此分组名为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操作
java
复制代码
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操作
java
复制代码
@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"; //定义容器contextPath public 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(); // 定义启动方法 @Override public void start() { server.start(); } // 定义停止方法 @Override public void stop() { server.stop(); } }
定义容器的dubbo.properties
ini
复制代码
dubbo.container=spring,jetty,log4j,undertow dubbo.undertow.path=/auth dubbo.undertow.port=8081
服务提供端
只需要设置 service.auth 为 true,表示该服务的调用需要鉴权认证通过。param.sign为true表示需要对参数也进行校验,之前的章节的内容我们已经介绍了对应的如何建立校验功能的实现机制控制。
java
复制代码
// (注解方式) @DubboService(parameters = {"service.auth","true"}) public class AuthServiceImpl implements AuthService { }
xml
复制代码
<?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.xsd http://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之后添加对应的内容
properties
复制代码
dubbo.auth.url=http://localhost:8081/auth
可以使用对应的配置工具进行获取配置信息。
java
复制代码
ConfigurationUtils.getProperty("dubbo.auth.url")
定义对应的服务提供端-建立服务鉴权过滤器
java
复制代码
@Override public 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
复制代码
<?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.xsd http://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>
模拟远程调用
java
复制代码
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 { @Autowired AuthSampleApi 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")); } }
既可以实现鉴权服务机制,大家还可以自己进行扩展实现。