我们正在为我们正在构建的自定义 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 使用哪个数据库?我意识到必须通过某种自定义身份验证过滤器来完成,但是有人可以指导我采用最合乎逻辑的方式吗?计划使用虚拟主机 url 告诉 shiro 和 mybatis 使用哪个 DB。
我是否为每个客户端创建一个领域?
Sc 1(由于 LDAP,用户名在客户端之间是唯一的)如果用户 jdoe 由 client1 和 client2 共享,并且他通过 client1 进行身份验证并尝试访问 client2 的资源,Shiro 会允许还是让他再次登录?
Sc 2(仅在数据库中唯一的用户名)如果客户端 1 和客户端 2 都创建了一个名为 jdoe 的用户,那么 Shiro 是否能够区分客户端 1 中的 jdoe 和客户端 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 Realm
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();
}
}
}