You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Micronaut实现Schema模式多租户?含认证及注册疑问

问题解答

一、改造AuthenticationProvider支持租户感知

你的推测完全正确——当前认证逻辑没有关联租户信息,导致无论访问哪个子域名,都会默认使用public schema的用户数据。需要按以下步骤改造:

  1. 先验证租户解析器是否生效
    先确认子域名租户解析是否正常工作,可添加一个请求拦截器打印当前租户ID:

    @InterceptorBean(TenantInterceptor.class)
    public class TenantLoggingInterceptor implements HttpRequestInterceptor {
        @Override
        public Publisher<MutableHttpRequest<?>> intercept(HttpRequest<?> request, HttpRequestInterceptorChain chain) {
            Optional<String> tenantId = TenantContext.currentTenantId();
            System.out.println("当前租户ID: " + tenantId.orElse("未识别"));
            return chain.proceed(request);
        }
    }
    

    访问subdomain.localhost时,如果控制台输出当前租户ID: subdomain,说明解析器生效;若未输出,需检查Micronaut版本是否支持该配置,或是否存在其他租户解析器冲突。

  2. 修改认证逻辑关联租户
    在AuthenticationProvider中获取当前租户ID,查询对应schema下的用户数据。如果使用JDBC操作,可动态拼接schema:

    @Singleton
    public class CustomAuthenticationProvider implements AuthenticationProvider {
        private final JdbcOperations jdbcOperations;
    
        public CustomAuthenticationProvider(JdbcOperations jdbcOperations) {
            this.jdbcOperations = jdbcOperations;
        }
    
        @Override
        public Publisher<AuthenticationResponse> authenticate(HttpRequest<?> request, AuthenticationRequest<?, ?> authRequest) {
            Optional<String> tenantId = TenantContext.currentTenantId();
            if (tenantId.isEmpty()) {
                return Publishers.just(AuthenticationResponse.failure("租户未识别"));
            }
            // 动态指定租户schema查询用户
            String sql = String.format("SELECT id, username, password FROM %s.users WHERE username = ?", tenantId.get());
            return jdbcOperations.findOne(sql, User.class, authRequest.getIdentity())
                    .map(user -> {
                        // 密码校验逻辑(示例用BCrypt)
                        if (BCrypt.checkpw(new String((char[]) authRequest.getSecret()), user.getPassword())) {
                            return AuthenticationResponse.success(user.getUsername(), Collections.singletonList("USER"));
                        } else {
                            return AuthenticationResponse.failure("密码错误");
                        }
                    })
                    .switchIfEmpty(Publishers.just(AuthenticationResponse.failure("用户不存在")));
        }
    }
    

    如果使用Micronaut Data Repository,可通过动态参数指定schema:

    @Repository
    public interface UserRepository extends CrudRepository<User, Long> {
        @Query("SELECT u FROM :schema.users u WHERE u.username = :username")
        Optional<User> findByUsernameAndSchema(@Param("schema") String schema, @Param("username") String username);
    }
    

    然后在认证逻辑中调用userRepository.findByUsernameAndSchema(tenantId.get(), (String) authRequest.getIdentity())即可。

二、未认证时创建租户Schema

新租户注册阶段无认证信息,TenantContext中没有租户ID,需手动处理Schema创建:

  1. 注册接口中手动执行Schema创建
    在注册接口接收租户标识(如子域名),先执行创建Schema的SQL,再初始化表结构:

    @Controller("/register")
    public class TenantRegistrationController {
        private final JdbcOperations jdbcOperations;
    
        public TenantRegistrationController(JdbcOperations jdbcOperations) {
            this.jdbcOperations = jdbcOperations;
        }
    
        @Post
        public HttpResponse<String> registerTenant(@Body TenantRegistrationRequest request) {
            String tenantId = request.getSubdomain();
            // 创建租户Schema
            jdbcOperations.execute(String.format("CREATE SCHEMA IF NOT EXISTS %s", tenantId));
            // 初始化表结构
            initializeTenantTables(tenantId);
            return HttpResponse.ok("租户注册成功");
        }
    
        private void initializeTenantTables(String tenantId) {
            String createUserTableSql = String.format("""
                CREATE TABLE IF NOT EXISTS %s.users (
                    id SERIAL PRIMARY KEY,
                    username VARCHAR(50) UNIQUE NOT NULL,
                    password VARCHAR(255) NOT NULL
                )
                """, tenantId);
            jdbcOperations.execute(createUserTableSql);
        }
    }
    
  2. 用数据库迁移工具规范初始化
    更可靠的方式是结合Flyway或Liquibase做Schema版本化管理,手动指定租户Schema执行迁移:

    private void initializeWithFlyway(String tenantId) {
        Flyway flyway = Flyway.configure()
                .dataSource(jdbcOperations.getDataSource())
                .schemas(tenantId)
                .locations("db/migration/tenant") // 存放租户通用迁移脚本的路径
                .load();
        flyway.migrate();
    }
    

    这种方式能确保所有租户的Schema结构一致,且支持后续的结构升级。

内容的提问来源于stack exchange,提问作者rich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:42:40