8

作成中のカスタムSaasアプリについてShiroを評価しています。優れたフレームワークは、箱から出して、私たちが望むことの90%を実行するようです。シロについての私の理解は基本であり、これが私が達成しようとしていることです。

  • 複数のクライアントがあり、それぞれが同一のデータベースを持っています
  • すべての承認(役割/許可)は、クライアントが専用のデータベース内で構成します
  • 各クライアントには、一意の仮想ホストがあります。client1.mycompany.com、client2.mycompany.comなど

シナリオ1

Authentication done via LDAP (MS Active Directory)
Create unique users in LDAP, make app aware of LDAP users, and have client admins provision them into whatever roles..

シナリオ2

Authentication also done via JDBC Relam in their database

質問:

Sc 1と2に共通どのデータベースを使用するかをShiroに伝えるにはどうすればよいですか?ある種のカスタム認証フィルターを介して実行する必要があることはわかっていますが、誰かが私を最も論理的な方法に導くことができますか?仮想ホストのURLを使用して、どのDBを使用するかをshiroとmybatisに指示することを計画します。

クライアントごとに1つのレルムを作成しますか?

Sc 1(LDAPにより、ユーザー名はクライアント間で一意です)ユーザーjdoeがclient1とclient2で共有され、client1を介して認証され、client2のリソースにアクセスしようとした場合、Shiroは許可するか、再度ログインさせますか?

Sc 2(データベース内で一意のユーザー名のみ)クライアント1とクライアント2の両方がjdoeというユーザーを作成した場合、Shiroはクライアント1のjdoeとクライアント2のjdoeを区別できますか?

レの入力に基づく私の解決策。

public class MultiTenantAuthenticator extends ModularRealmAuthenticator {

    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        TenantAuthenticationToken tat = null;
        Realm tenantRealm = null;

        if (!(authenticationToken instanceof TenantAuthenticationToken)) {
            throw new AuthenticationException("Unrecognized token , not a typeof TenantAuthenticationToken ");
        } else {
            tat = (TenantAuthenticationToken) authenticationToken;
            tenantRealm = lookupRealm(tat.getTenantId());
        }

        return doSingleRealmAuthentication(tenantRealm, tat);

    }

    protected Realm lookupRealm(String clientId) throws AuthenticationException {
        Collection<Realm> realms = getRealms();
        for (Realm realm : realms) {
            if (realm.getName().equalsIgnoreCase(clientId)) {
                return realm;
            }
        }
        throw new AuthenticationException("No realm configured for Client " + clientId);
    }
}

新しいタイプのトークン。

public final class TenantAuthenticationToken extends UsernamePasswordToken {

       public enum TENANT_LIST {

            CLIENT1, CLIENT2, CLIENT3 
        }
        private String tenantId = null;

        public TenantAuthenticationToken(final String username, final char[] password, String tenantId) {
            setUsername(username);
            setPassword(password);
            setTenantId(tenantId);
        }

        public TenantAuthenticationToken(final String username, final String password, String tenantId) {
            setUsername(username);
            setPassword(password != null ? password.toCharArray() : null);
            setTenantId(tenantId);
        }

        public String getTenantId() {
            return tenantId;
        }

        public void setTenantId(String tenantId) {
            try {
                TENANT_LIST.valueOf(tenantId);
            } catch (IllegalArgumentException ae) {
                throw new UnknownTenantException("Tenant " + tenantId + " is not configured " + ae.getMessage());
            }
            this.tenantId = tenantId;
        }
    }

継承したJDBCレルムを変更する

public class TenantSaltedJdbcRealm extends JdbcRealm {

    public TenantSaltedJdbcRealm() {
        // Cant seem to set this via beanutils/shiro.ini
        this.saltStyle = SaltStyle.COLUMN;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return super.supports(token) && (token instanceof TenantAuthenticationToken);
    }

そして最後に、ログイン時に新しいトークンを使用します

// This value is set via an Intercepting Servlet Filter
String client = (String)request.getAttribute("TENANT_ID");

        if (!currentUser.isAuthenticated()) {
            TenantAuthenticationToken token = new TenantAuthenticationToken(user,pwd,client);
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  "
                        + "Please contact your administrator to unlock it.");
            } // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
                ae.printStackTrace();
            }
        }

}
4

1 に答える 1

10

おそらく、すべてのリクエストの前に配置され、リクエストに関連するtenantIdを解決するServletFilterが必要になります。その解決されたtenantIdをリクエスト属性またはスレッドローカルとして保存して、リクエストの期間中どこでも利用できるようにすることができます。

次のステップは、おそらくAuthenticationTokenのサブインターフェースを作成することです。たとえばTenantAuthenticationToken、メソッドがgetTenantId()あります。これは、リクエスト属性またはスレッドローカルによって入力されます。(例:getTenantId()=='client1'または'client2'など)。

次に、レルムの実装はトークンとその実装を検査し、トークンがインスタンスであり、レルムがその特定のテナントのデータストアと通信している場合にのみsupports(AuthenticationToken)戻ります。trueTenantAuthenticationToken

これは、クライアントデータベースごとに1つのレルムを意味します。ただし、注意してください。これをクラスターで実行し、任意のクラスターノードが認証要求を実行できる場合、すべてのクライアントノードがすべてのクライアントデータベースに接続できる必要があります。承認データ(ロール、グループ、権限など)もデータベース間で分割されている場合、承認についても同じことが言えます。

環境によっては、クライアントの数によってはこれが適切に拡張されない場合があります。それに応じて判断する必要があります。

JNDIリソースについては、はい、ShiroのJndiObjectFactoryを介してShiroINIで参照できます。

[main]
datasource = org.apache.shiro.jndi.JndiObjectFactory
datasource.resourceName = jdbc/mydatasource
# if the JNDI name is prefixed with java:comp/env (like a Java EE environment),
# uncomment this line:
#datasource.resourceRef = true

jdbcRealm = com.foo.my.JdbcRealm
jdbcRealm.datasource = $datasource

ファクトリはデータソースを検索し、INIで直接宣言されているかのように他のBeanで利用できるようにします。

于 2012-02-17T17:29:47.487 に答える