Многоквартирный дом в Широ
Мы оцениваем Shiro для пользовательского приложения Saas, которое мы создаем. Похоже, что отличный фреймворк делает 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-адреса виртуального хоста, чтобы указать широ и mybatis, какую БД использовать.
Я создаю одну область на клиента?
Sc 1 (Имена пользователей уникальны для всех клиентов благодаря LDAP). Если пользователь jdoe является общим для client1 и client2, и он аутентифицирован через client1 и пытается получить доступ к ресурсу client2, разрешит ли Широ или он снова войдет в систему?
Sc 2 (имена пользователей уникальны только в базе данных). Если и клиент 1, и клиент 2 создают пользователя с именем jdoe, то сможет ли Широ различать jdoe в клиенте 1 и jdoe в клиенте 2?
Мое решение основано на вкладе Леса..
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();
}
}
}
1 ответ
Вам, вероятно, понадобится ServletFilter, который находится перед всеми запросами и разрешает tenantId, относящийся к запросу. Вы можете сохранить этот разрешенный tenantId в качестве атрибута запроса или локального потока, чтобы он был доступен в любом месте на время запроса.
Следующим шагом, вероятно, будет создание подчиненного интерфейса AuthenticationToken, например TenantAuthenticationToken
у этого есть метод: getTenantId()
, который заполняется вашим атрибутом запроса или локальным потоком. (например, getTenantId() == 'client1' или 'client2' и т. д.).
Затем ваши реализации Realm могут проверять токен и supports(AuthenticationToken)
реализация и возврат true
только если токен TenantAuthenticationToken
экземпляр и Царство связывается с хранилищем данных для этого конкретного арендатора.
Это подразумевает одну область на клиентскую базу данных. Однако будьте осторожны - если вы делаете это в кластере, и любой узел кластера может выполнить запрос аутентификации, каждый клиентский узел должен иметь возможность подключаться к каждой клиентской базе данных. То же самое будет верно для авторизации, если данные авторизации (роли, группы, разрешения и т. Д.) Также распределены по базам данных.
В зависимости от вашей среды, это может плохо масштабироваться в зависимости от количества клиентов - вам нужно судить соответственно.
Что касается ресурсов JNDI, то да, вы можете ссылаться на них в INI Shiro через JndiObjectFactory Shiro:
[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.