pulsar自定义认证插件开发
Linux MacBook单机部署Pulsar并开启认证功能
pulsar集群搭建_亲测成功
pulsar的权限分为两部分:
1 客户端连接时认证的权限 (前面的博客讲解了)
2 客户端创建,发送和订阅主题的权限
默认情况下,pulsar是不会开启客户端连接认证和主题的权限控制,即客户端到broker之间、broker到broker之间的访问都没有任何限制。但是在线上环境中,对于权限的控制往往是很重要的。连接认证和主题的权限是两个独立的功能,(连接认证前面博客已开启)可以分别开启
如果要开启对权限的控制,首先需要打开对连接的认证。
修改配置,开启主题权限功能
standalone.conf 或 broker.conf
#开启权限
authorizationEnabled=true
# 自定义权限类,这个类需要我们自己实现,继承org.apache.pulsar.broker.authorization.AuthorizationProvider接口即可
authorizationProvider=com.beyond.authz.PulsarAuthorizationProviderMysql
#配置超级权限
superUserRoles=admin,test
pulsar提供了AuthorizationProvider接口。我们自定义权限实现接口
package com.beyond.authz;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.pulsar.broker.ServiceConfiguration;
import org.apache.pulsar.broker.authentication.AuthenticationDataSource;
import org.apache.pulsar.broker.authorization.AuthorizationProvider;
import org.apache.pulsar.broker.cache.ConfigurationCacheService;
import org.apache.pulsar.broker.resources.PulsarResources;
import org.apache.pulsar.common.naming.NamespaceName;
import org.apache.pulsar.common.naming.TopicName;
import org.apache.pulsar.common.policies.data.*;
import org.apache.pulsar.common.util.FutureUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class PulsarAuthorizationProviderMysql implements AuthorizationProvider {
private static final Logger log = LoggerFactory.getLogger(PulsarAuthorizationProviderMysql.class);
public ServiceConfiguration conf;
private PulsarResources pulsarResources;
@Override
public void initialize(ServiceConfiguration conf, ConfigurationCacheService configCache) throws IOException {
log.info("PulsarAuthorizationProviderMysql.initialize*******************************");
this.conf = conf;
this.pulsarResources = configCache.getPulsarResources();
}
@Override
public CompletableFuture<Boolean> canProduceAsync(TopicName topicName, String role, AuthenticationDataSource authenticationData) {
log.info("PulsarAuthorizationProviderMysql.canProduceAsync***************************topicname:{}, role={}", topicName.toString(), role);
return mysqlQuery(topicName, role);
}
private CompletableFuture<Boolean> mysqlQuery(TopicName topicName, String role) {
//查询数据库,看有没有权限
String aaa = MysqlConfig.queryAuthByTenantAndnamespace("select aaa from test where bbb = ? and tenant = ? and namespace = ?", role, topicName.getTenant(), topicName.getNamespacePortion());
log.info("mysql queryAuthByTenantAndnamespace ********{}, {}, {}, {}", role, topicName.getTenant(), topicName.getNamespacePortion(), aaa);
CompletableFuture<Boolean> permissionFuture = new CompletableFuture<>();
if (aaa != null) {
permissionFuture.complete(true);
} else {
log.error("mysqlQuery****{},{}**********result*****{}", topicName.toString(), role, aaa);
permissionFuture.complete(false);
}
return permissionFuture;
}
@Override
public CompletableFuture<Boolean> canConsumeAsync(TopicName topicName, String role, AuthenticationDataSource authenticationData, String subscription) {
log.info("PulsarAuthorizationProviderMysql.canConsumeAsync***************************topicname:{}, role={}", topicName.toString(), role);
return mysqlQuery(topicName, role);
}
@Override
public CompletableFuture<Boolean> canLookupAsync(TopicName topicName, String role, AuthenticationDataSource authenticationData) {
log.info("PulsarAuthorizationProviderMysql.canLookupAsync***************************topicname:{}, role={}", topicName.toString(), role);
return mysqlQuery(topicName, role);
}
@Override
public CompletableFuture<Boolean> allowFunctionOpsAsync(NamespaceName namespaceName, String role, AuthenticationDataSource authenticationData) {
return defaultResult(true, "allowFunctionOpsAsync", namespaceName, role);
}
@Override
public CompletableFuture<Boolean> allowSourceOpsAsync(NamespaceName namespaceName, String role, AuthenticationDataSource authenticationData) {
return defaultResult(true, "allowSourceOpsAsync", namespaceName, role);
}
@Override
public CompletableFuture<Boolean> allowSinkOpsAsync(NamespaceName namespaceName, String role, AuthenticationDataSource authenticationData) {
return defaultResult(true, "allowSinkOpsAsync", namespaceName, role);
}
@Override
public CompletableFuture<Void> grantPermissionAsync(NamespaceName namespace, Set<AuthAction> actions, String role, String authDataJson) {
return defaultVoidResult("grantPermissionAsync", namespace, role);
}
@Override
public CompletableFuture<Void> grantSubscriptionPermissionAsync(NamespaceName namespace, String subscriptionName, Set<String> roles, String authDataJson) {
return defaultVoidResult("grantSubscriptionPermissionAsync", namespace, roles.toString());
}
@Override
public CompletableFuture<Void> revokeSubscriptionPermissionAsync(NamespaceName namespace, String subscriptionName, String role, String authDataJson) {
return defaultVoidResult("revokeSubscriptionPermissionAsync", namespace, role);
}
@Override
public CompletableFuture<Void> grantPermissionAsync(TopicName topicName, Set<AuthAction> actions, String role, String authDataJson) {
return defaultVoidResult("grantPermissionAsync", topicName, role);
}
@Override
public void close() {
log.info("PulsarAuthorizationProviderMysql.close********************************");
}
@Override
public CompletableFuture<Boolean> allowTenantOperationAsync(String tenantName, String role, TenantOperation operation, AuthenticationDataSource authData) {
log.info("PulsarAuthorizationProviderMysql.allowTenantOperationAsync********************************tenantName={}, role={}", tenantName, role);
CompletableFuture<Boolean> permissionFuture = new CompletableFuture<>();
permissionFuture.complete(true);
return permissionFuture;
}
@Override
public CompletableFuture<Boolean> allowNamespaceOperationAsync(NamespaceName namespaceName, String role, NamespaceOperation operation, AuthenticationDataSource authData) {
return defaultResult(true, "allowNamespaceOperationAsync", namespaceName, role);
}
@Override
public CompletableFuture<Boolean> allowNamespacePolicyOperationAsync(NamespaceName namespaceName, PolicyName policy, PolicyOperation operation, String role, AuthenticationDataSource authData) {
return defaultResult(true, "allowNamespacePolicyOperationAsync", namespaceName, role);
}
@Override
public CompletableFuture<Boolean> allowTopicOperationAsync(TopicName topic, String role, TopicOperation operation, AuthenticationDataSource authData) {
log.info("PulsarAuthorizationProviderMysql.allowTopicOperationAsync********************************topicName={}, role={}, topicOperation={}",
topic.toString(), role, operation.toString());
CompletableFuture<Boolean> isAuthorizedFuture;
switch (operation) {
case LOOKUP:
case GET_STATS:
return canLookupAsync(topic, role, authData);
case PRODUCE:
return canProduceAsync(topic, role, authData);
case GET_SUBSCRIPTIONS:
case CONSUME:
case SUBSCRIBE:
case UNSUBSCRIBE:
case SKIP:
case EXPIRE_MESSAGES:
case PEEK_MESSAGES:
case RESET_CURSOR:
return canConsumeAsync(topic, role, authData, authData.getSubscription());
case TERMINATE:
case COMPACT:
case OFFLOAD:
case UNLOAD:
case ADD_BUNDLE_RANGE:
case GET_BUNDLE_RANGE:
case DELETE_BUNDLE_RANGE:
return validateTenantAdminAccess(topic.getTenant(), role, authData);
default:
return FutureUtil.failedFuture(
new IllegalStateException("TopicOperation [" + operation.name() + "] is not supported."));
}
}
public CompletableFuture<Boolean> validateTenantAdminAccess(String tenantName,
String role,
AuthenticationDataSource authData) {
return isSuperUser(role, authData, conf)
.thenCompose(isSuperUser -> {
if (isSuperUser) {
return CompletableFuture.completedFuture(true);
} else {
return CompletableFuture.completedFuture(false);
}
});
}
public boolean isSuper(String role, AuthenticationDataSource authData) {
//超级管理员
CompletableFuture<Boolean> isSuccess = isSuperUser(role, authData, conf)
.thenCompose(isSuperUser -> {
if (isSuperUser) {
return CompletableFuture.completedFuture(true);
} else {
return CompletableFuture.completedFuture(false);
}
});
try {
return isSuccess.get();
} catch (Exception e) {
e.printStackTrace();
log.error("isSuper***********error:{}", e.getMessage());
}
return false;
}
@Override
public CompletableFuture<Boolean> allowTopicPolicyOperationAsync(TopicName topic, String role, PolicyName policy, PolicyOperation operation, AuthenticationDataSource authData) {
return defaultResult(true, "allowTopicPolicyOperationAsync", topic, role);
}
CompletableFuture<Boolean> defaultResult(boolean isPermission, String name, TopicName topicName, String role) {
log.info("PulsarAuthorizationProviderMysql.defaultResult*******************************func name={},topicname:{}, role={}", name, topicName.toString(), role);
CompletableFuture<Boolean> permissionFuture = new CompletableFuture<>();
permissionFuture.complete(isPermission);
return permissionFuture;
}
CompletableFuture<Boolean> defaultResult(boolean isPermission, String name, NamespaceName namespaceName, String role) {
log.info("PulsarAuthorizationProviderMysql.defaultResult*******************************func name={},namespacename:{}, role={}", name, namespaceName.toString(), role);
CompletableFuture<Boolean> permissionFuture = new CompletableFuture<>();
//true有权限
permissionFuture.complete(isPermission);
return permissionFuture;
}
CompletableFuture<Void> defaultVoidResult(String name, TopicName topicName, String role) {
log.info("PulsarAuthorizationProviderMysql.defaultVoidResult*******************************func name={},topicname:{}, role={}", name, topicName.toString(), role);
CompletableFuture<Void> permissionFuture = new CompletableFuture<>();
permissionFuture.complete(null);
return permissionFuture;
}
CompletableFuture<Void> defaultVoidResult(String name, NamespaceName namespaceName, String role) {
log.info("PulsarAuthorizationProviderMysql.defaultVoidResult*******************************func name={},namespacename:{}, role={}", name, namespaceName.toString(), role);
CompletableFuture<Void> permissionFuture = new CompletableFuture<>();
permissionFuture.complete(null);
return permissionFuture;
}
}
maven引入相关依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.22version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.apache.pulsargroupId>
<artifactId>pulsar-brokerartifactId>
<version>2.8.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
编写好代码后用maven命令打包:
mvn clean install -Dmaven.test.skip=true
然后把打好的jar包放到pulsar的lib目录下,如:apache-pulsar-2.8.1/lib,
重启broker组件即可生效。
package com.beyond.auth;
import org.apache.pulsar.client.api.*;
public class TestPulsarAuth {
public static void main(String[] args) throws Exception {
PulsarClient client = PulsarClient.builder()
.serviceUrl("pulsar://127.0.0.1:6650")
//admin有全部权限,配置一个没有权限的角色放数据库测试
.authentication("com.beyond.auth.AuthenticationMysql", "e3592948c:admin:123456")
.build();
//订阅
ConsumerThreadAuth consumerThread = new ConsumerThreadAuth(client);
consumerThread.start();
//发布
Producer<String> stringProducer = client.newProducer(Schema.STRING)
.topic("persistent://my-tenant/my-namespace/aaa")
.create();
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
stringProducer.send(i + "aaaaaaaa111112222333");
}
}
}
class ConsumerThreadAuth extends Thread {
private PulsarClient client;
public ConsumerThreadAuth(PulsarClient client) throws Exception {
this.client = client;
}
@Override
public void run() {
try {
Consumer consumer = client.newConsumer()
.topicsPattern("persistent://my-tenant/my-namespace/aaa")
.subscriptionName("my-subscription")
.subscriptionType(SubscriptionType.Shared)
.subscribe();
while (true) {
// Wait for a message
Message msg = consumer.receive();
try {
// Do something with the message
System.out.println(msg.getTopicName() + "222222Message received: " + new String(msg.getData()));
// Acknowledge the message so that it can be deleted by the message broker
consumer.acknowledge(msg);
} catch (Exception e) {
// Message failed to process, redeliver later
consumer.negativeAcknowledge(msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
pulsar的权限是在role的层次上进行控制的。在客户端连接认证成功后,会保存客户端的role,然后根据不同的接口结合role和具体操作进行权限限制。
比如:数据库对每个role进行权限管理,当客户端连接pulsar时,把用户名当成role,连接成功后pular会记录此客户端的用户名,当客户端发起订阅和发布主题时,会调用对应的权限实现接口里对应的方法,只要拿到用户名role和主题去数据库查询是否有权限即可.
参考链接:
https://blog.csdn.net/casuallc/article/details/119151911