Shiro 的多租户
Posted
技术标签:
【中文标题】Shiro 的多租户【英文标题】:Multi tenancy in Shiro 【发布时间】:2012-03-05 07:51:15 【问题描述】:我们正在为我们正在构建的自定义 Saas 应用评估 Shiro。似乎一个伟大的框架可以完成我们想要的 90% 的工作,开箱即用。我对 Shiro 的理解是基本的,这就是我想要完成的。
我们有多个客户,每个客户都有一个相同的数据库 所有授权(角色/权限)将由客户端配置 在他们自己的专用数据库中 每个客户端都有一个唯一的 虚拟主机例如。 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 使用哪个数据库?一世 意识到必须通过某种自定义身份验证来完成 过滤器,但是有人可以指导我采用最合乎逻辑的方式吗?计划使用 用于告诉 shiro 和 mybatis 使用哪个 DB 的虚拟主机 url。
我是否为每个客户端创建一个领域?
Sc 1(由于 LDAP,用户名在客户端之间是唯一的)如果用户 jdoe 由client1和client2共享,通过client1认证 并尝试访问 client2 的资源,Shiro 是否允许或有 他又登录了?
Sc 2(用户名仅在数据库中唯一)如果客户端 1 和 客户端2创建一个名为jdoe的用户,然后Shiro就可以 区分Client 1中的jdoe和Client 2中的jdoe?
我的解决方案基于 Les 的意见..
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();
【问题讨论】:
如果我在 Tomcat 中配置 JDBC 资源,我可以让 shiro 获取它而不是在 shiro.ini 中重新定义它们吗? 【参考方案1】:您可能需要一个 ServletFilter,它位于所有请求的前面并解析与请求相关的租户 ID。您可以将解析后的tenantId 存储为请求属性或线程本地,以便在请求期间任何地方都可用。
下一步可能是创建一个 AuthenticationToken 的子接口,例如TenantAuthenticationToken
有一个方法:getTenantId()
,它由您的请求属性或线程本地填充。 (例如 getTenantId() == 'client1' 或 'client2' 等)。
然后,您的 Realm 实现可以检查令牌及其 supports(AuthenticationToken)
实现,并且仅当令牌是 TenantAuthenticationToken
实例并且 Realm 正在与该特定租户的数据存储进行通信时才返回 true
。
这意味着每个客户端数据库有一个领域。但请注意 - 如果您在集群中执行此操作,并且任何集群节点都可以执行身份验证请求,则 每个 客户端节点将需要能够连接到 每个 客户端数据库。如果授权数据(角色、组、权限等)也跨数据库分区,则授权也是如此。
根据您的环境,这可能无法很好地扩展,具体取决于客户端的数量 - 您需要做出相应的判断。
至于JNDI资源,是的,你可以通过Shiro的JndiObjectFactory在Shiro INI中引用它们:
[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
工厂将查找数据源并将其提供给其他 bean,就像它直接在 INI 中声明一样。
【讨论】:
感谢一百万 @Les Hazelwood。 @aks 很高兴为您提供帮助!你能奖励答案吗? 对 SO 还是新手,所以没有意识到有这样的概念.. 再次感谢您及时、清晰和有用的回复。以上是关于Shiro 的多租户的主要内容,如果未能解决你的问题,请参考以下文章