Apache Shiro authentication against LDAP - any username/password combination gets through - spring

I'm developing a web application using Spring, Vaadin and Apache Shiro for authentication and authorization. I have two realms, since some users log in through a database, others authenticate against LDAP. JDBC realm works perfectly but somehow LDAP realm lets everybody through - no matter what username/password combination is provided.
Here is my Spring configuration:
<!-- Apache Shiro -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<ref bean="jdbcRealm" />
<ref bean="ldapRealm" />
</list>
</property>
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
</property>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean id="ldapContextFactory" class="org.apache.shiro.realm.ldap.JndiLdapContextFactory">
<property name="url" value="ldap://localhost:389" />
</bean>
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="ldapRealm" class="org.apache.shiro.realm.ldap.JndiLdapRealm">
<property name="contextFactory" ref="ldapContextFactory" />
<property name="userDnTemplate" value="uid={0},ou=people,dc=maxcrc,dc=com" />
</bean>
Logging in is rather typical:
try {
// Obtain user reference
Subject currentUser = SecurityUtils.getSubject();
// Create token using provided username and password
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
// Remember user
if(rememberMe.getValue())
token.setRememberMe(true);
// Login
currentUser.login(token);
// If we are here, no exception was raised and the user was logged in, so redirect
UI.getCurrent().getNavigator().navigateTo("main" + "/" + "main-page");
// Fire CustomEvent
fireEvent(new CustomEvent(ErasmusLoginForm.this));
} catch ( UnknownAccountException e ) {
Notification.show("No such user...");
} catch ( IncorrectCredentialsException e ) {
Notification.show("Invalid creditentials...");
} catch ( LockedAccountException e ) {
Notification.show("Locked account...");
} catch ( AuthenticationException e ) {
e.printStackTrace();
Notification.show("Some other exception...");
} catch (Exception e) {
// Password encryption exception
}
I read almost everywhere with no luck.
This post (Shiro Authenticates Non-existent User in LDAP) also wasn't helpful to me - both the DN template and the URL are correct and the server (LDAP server) is running. Why does it let everybody through?
If I turn Ldap realm off, JDBC authentication works perfectly. But with both of them on, everybody gets through since I'm using FirstSuccessfulStrategy.
EDIT: Additional note: if I provide an empty password, AuthenticationException is raised. But any non-empty password works fine.
Any ideas?

Related

How can I redirect a user after successful login to different pages, based on their previous page?

I'm redirecting a user to the homepage with the "Default target-url" being set to "/". However, I need to redirect a user if they login on either a product page (/p/) or a search page (/search). How could I go about doing this? I'm not all that knowledgeable about Spring Security and redirects yet.
I've tried intercepting the request within the onAuthenticationSuccess() method in my AuthenticationSuccessHandler and checking for the URL if it contains the product page or search page url.
Within the AuthenticationSuccessHandler:
if (!response.isCommitted()) {
super.onAuthenticationSuccess(request,response,authentication);
}
Within the spring-security-config.xml:
<bean id="authenticationSuccessHandler" class ="com.storefront.AuthenticationSuccessHandler" scope="tenant">
<property name="rememberMeCookieStrategy" ref="rememberMeCookieStrategy" />
<property name="customerFacade" ref="customerFacade" />
<property name="sCustomerFacade" ref="sCustomerFacade" />
<property name="sProductFacade" ref="sProductFacade" />
<property name="defaultTargetUrl" value="/" />
<property name="useReferer" value="true" />
<property name="requestCache" value="httpSessionRequestCache" />
The expected results will be:
When a user logs in on a product page, they will be returned to the product page they were on.
When a user logs in on a search page, they will be returned to the search page they were on.
If the user logs in while not on a product or search page, they are redirected to the homepage.
Hybris OOTB (I'm referring to V6.7) has a functionality where you can list the URLs for which you want to redirect to Default Target Url. The idea here is to have another list(or replace the existing one) with the reverse logic which only allows the given URLs and redirects all other URLs to the default target URLs.
In OOTB you can see listRedirectUrlsForceDefaultTarget in the spring-security-config.xml, where can define the list of URLs which you want to redirect to the default target. Like below.
<alias name="defaultLoginAuthenticationSuccessHandler" alias="loginAuthenticationSuccessHandler"/>
<bean id="defaultLoginAuthenticationSuccessHandler" class="de.hybris.platform.acceleratorstorefrontcommons.security.StorefrontAuthenticationSuccessHandler" >
<property name="customerFacade" ref="customerFacade" />
<property name="defaultTargetUrl" value="#{'responsive' == '${commerceservices.default.desktop.ui.experience}' ? '/' : '/my-account'}"/>
<property name="useReferer" value="true"/>
<property name="requestCache" ref="httpSessionRequestCache" />
<property name="uiExperienceService" ref="uiExperienceService"/>
<property name="cartFacade" ref="cartFacade"/>
<property name="customerConsentDataStrategy" ref="customerConsentDataStrategy"/>
<property name="cartRestorationStrategy" ref="cartRestorationStrategy"/>
<property name="forceDefaultTargetForUiExperienceLevel">
<map key-type="de.hybris.platform.commerceservices.enums.UiExperienceLevel" value-type="java.lang.Boolean">
<entry key="DESKTOP" value="false"/>
<entry key="MOBILE" value="false"/>
</map>
</property>
<property name="bruteForceAttackCounter" ref="bruteForceAttackCounter" />
<property name="restrictedPages">
<list>
<value>/login</value>
</list>
</property>
<property name="listRedirectUrlsForceDefaultTarget">
<list>/example/redirect/todefault</list>
</property>
</bean>
StorefrontAuthenticationSuccessHandler
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
final Authentication authentication) throws IOException, ServletException
{
//...
//if redirected from some specific url, need to remove the cachedRequest to force use defaultTargetUrl
final RequestCache requestCache = new HttpSessionRequestCache();
final SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null)
{
for (final String redirectUrlForceDefaultTarget : getListRedirectUrlsForceDefaultTarget())
{
if (savedRequest.getRedirectUrl().contains(redirectUrlForceDefaultTarget))
{
requestCache.removeRequest(request, response);
break;
}
}
}
//...
}
Now reverse that logic by declaring new list (let's say listAllowedRedirectUrls ) or replacing listRedirectUrlsForceDefaultTarget with listAllowedRedirectUrls in the spring-security-config.xml and do the respective changes in the SuccessHandler. Like
<property name="listAllowedRedirectUrls">
<list>/p/</list>
<list>/search</list>
</property>
StorefrontAuthenticationSuccessHandler
if (savedRequest != null)
{
for (final String listAllowedRedirectUrl : getListAllowedRedirectUrls())
{
if ( ! savedRequest.getRedirectUrl().contains(listAllowedRedirectUrl))
{
requestCache.removeRequest(request, response);
break;
}
}
}
You have to do the same changes for /login/checkout handler declaration (defaultLoginCheckoutAuthenticationSuccessHandler) as well.
You can extend AbstractLoginPageController for running your own scenario. Save referrer URL in doLogin method to httpSessionRequestCache. Then check and filter and return referrer URL in getSuccessRedirect method.

Spring LDAP Authentication UnknownHostException

I am trying to do simple AD authentication using Spring LDAP. Below is my config xml
<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
<property name="url" value="ldap://ValidADhost:port" />
<property name="base" value="dc=ad,dc=XXX,dc=com"/>
<property name="userDn" value="ValidUserName" />
<property name="password" value="ValidPassword" />
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>
Authentication code:
public boolean login(String username, String password) {
AndFilter filter = new AndFilter();
ldapTemplate.setIgnorePartialResultException(true);
filter.and(new EqualsFilter("sAMAccountName", username));
return ldapTemplate.authenticate("", filter.toString(), password);
}
With this code, Im getting the following Exception
HTTP Status 500 - Request processing failed; nested exception is org.springframework.ldap.CommunicationException:ValidADhost:port;nested exception is javax.naming.CommunicationException:ValidADhost:port[Root exception is java.net.UnknownHostException:ValidADhost:port]
I am able to get the user details from the same LDAP hostname:port in C# test program.
I appreciate any help/pointers/solutions.

TransactionTemplate and TransactionManager connection objects

The following code in my DAO works perfectly fine.
public void insert(final Person person) {
transactionTemplate.execute(new TransactionCallback<Void>() {
public Void doInTransaction(TransactionStatus txStatus) {
try {
getJdbcTemplate().execute("insert into person(username, password) values ('" + person.getUsername() + "','" + person.getPassword() + "')");
} catch (RuntimeException e) {
txStatus.setRollbackOnly();
throw e;
}
return null;
}
});
}
Following is my spring config.
<bean id="derbyds" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="username" value="app" />
<property name="password" value="app" />
<property name="url" value="jdbc:derby:mytempdb" />
</bean>
<bean id="persondaojdbc" class="com.napp.dao.impl.PersonDaoJdbcImpl">
<property name="dataSource" ref="derbyds" />
<property name="transactionTemplate">
<bean class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="derbyds"/>
</bean>
What i wanted to know is how does the
<bean id="persondaojdbc" class="com.napp.dao.impl.PersonDaoJdbcImpl">
<property name="dataSource" ref="derbyds" />
<property name="transactionTemplate">
<bean class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="derbyds"/>
</bean>
Now, its imperative that both the TransactionManager and the code (in my case jdbc template) operate on the same connection. I am assuming both of them are getting the Connection objects from the DataSource. DataSource pools connections and its a chance when you call getConnection multiple times, you will get different Connection obejcts. How does spring make sure that the TransactionManager and JdbcTemplate end up getting the same connection objects. My understanding is that, if that doesn't happen, rollbacks, or commits wont work, correct? Could someone throw more light on this.
If you look at the code for JdbcTemplate (one of the execute(...) methods) you will see
Connection con = DataSourceUtils.getConnection(getDataSource());
Which tries to retrieve a Connection from a ConnectionHolder registered with a TransactionSynchronizationManager.
If there is no such object, it just gets a connection from the DataSource and registers it (if it is in a transactional environment, ie. you have a transaction manager). Otherwise, it immediately returns the registered object.
This is the code (stripped of logs and stuff)
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
Connection con = dataSource.getConnection();
// flag set by the TransactionManager
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
You'll notice that the JdbcTemplate tries to
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
release the Connection, but this only happens if you're in a non-transactional environment, ie.
if it is not managed externally (that is, not bound to the thread).
Therefore, in a transactional world, the JdbcTemplate will be reusing the same Connection object.

How to use separate realms for authentication and authorization with Shiro and CAS?

I'm working on a web application where multiple applications authenticates through a CAS SSO Server. Howerver, each application should maintain their respective roles and these roles are stored in a database specific to the application. So, I need to have 2 realms, one for CAS (for authc) and another for DB (for authz).
This is my current shiro config. I'm getting the redirection to the CAS working properly, but the logged in user (Subject) doesn't seems to have the roles/permission loaded in it (e.g. SecurityUtil.isPermitted() not working as expected)
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="name" value="jdbcRealm" />
<property name="dataSource" ref="dataSource" />
<property name="authenticationQuery"
value="SELECT password FROM system_user_accounts WHERE username=? and status=10" />
<property name="userRolesQuery"
value="SELECT role_code FROM system_roles r, system_user_accounts u, system_user_roles ur WHERE u.user_id=ur.user_id AND r.role_id=ur.role_id AND u.username=?" />
<property name="permissionsQuery"
value="SELECT code FROM system_roles r, system_permissions p, system_role_permission rp WHERE r.role_id=rp.role_id AND p.permission_id=rp.permission_id AND r.role_code=?" />
<property name="permissionsLookupEnabled" value="true"></property>
<property name="cachingEnabled" value="true" />
<property name="credentialsMatcher" ref="passwordMatcher" />
</bean>
<!-- For CAS -->
<bean id="casRealm" class="org.apache.shiro.cas.CasRealm">
<property name="defaultRoles" value="ROLE_USER" />
<property name="casServerUrlPrefix" value="http://localhost:7080/auth" />
<property name="casService" value="http://localhost:8080/hawk-hck-web/shiro-cas" />
<property name="validationProtocol" value="SAML" />
<property name="cachingEnabled" value="true"></property>
</bean>
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />
<!-- Security Manager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<ref bean="casRealm" />
<ref bean="jdbcRealm" />
</list>
</property>
<property name="cacheManager" ref="cacheManager"/>
<property name="subjectFactory" ref="casSubjectFactory" />
</bean>
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<property name="failureUrl" value="/error"></property>
</bean>
<!-- Shiro filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="http://localhost:7080/auth/login?service=http://localhost:8080/hawk-hck-web/shiro-cas" />
<property name="successUrl" value="/home/index" />
<property name="unauthorizedUrl" value="/error" />
<property name="filters">
<util:map>
<entry key="casFilter" value-ref="casFilter" />
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
<!-- !!! Order matters !!! -->
/shiro-cas = casFilter
/login = anon
/logout = logout
/error = anon
/static/** = anon
/** = authc
</value>
</property>
</bean>
The way I register the realms with the securityManager should be in correct. I can't really find a good example of the setup.
I have 2 questions here:
What is correct setup/configuration to achieve above mentioned scenario?
What is the best practice to manage users and roles across different/seperate applications?
The problem you are running into has to do with the fact that both CasRealm and JdbcRealm extends both AuthorizingRealm (Authorizer) and AuthenticatingRealm. First step I would take is with the JdbcRealm. The JdbcRealm implementation inherits the AuthenticatingRealm#supports(AuthenticationToken token) method implementation. If you extend JdbcRealm and override the "supports" method to return "false" for all token types the JdbcRealm will no longer be used for authentication purposes.
#Override
public boolean supports (AuthenticationToken token) {
return false;
}
The CasRealm is a different story, there is no way (that I know of) to easily tell Shiro to not use a realm that implements Authorizer when checking permissions. I personally find it frustrating that the default implementation for most protocols assumes that both authorization and authentication are needed. I would prefer each to be split into two implementations (eg AuthenticatingCasRealm, AuthorizingCasRealm).
The logic behind checking permissions when multiple realms are in use is documented here. The specific text that references this behavior is:
Step 4: Each configured Realm is checked to see if it implements the
same Authorizer interface. If so, the Realm's own respective hasRole*,
checkRole*, isPermitted*, or checkPermission* method is called.
Based on this, you theoretically could override each of the named methods and all of their overloaded implementations to always return "false".
My solution to this problem is based on my prior comment about splitting each realm into two components, one for authentication and one for authorization. You end up with more duplicate code this way but it is explicit in what behaviors you are expecting from your implementation.
Here's how to go about it:
Create a new class "AuthenticatingCasRealm" that extends org.apache.shiro.realm.AuthenticatingRealm and implements org.apache.shiro.util.Initializable.
Copy and paste the contents of the existing CasRealm source into your new "AuthenticatingCasRealm" class. (I am aware that taking a copy-and-paste route of existing code is often frowned upon however in the described circumstsance I know of no other way of solving the problem.)
Strip out all methods that were implemented for org.apache.shiro.realm.AuthorizingRealm.
Update your Shrio configuration to reference your new AuthenticatingCasRealm implementation.
Based on these changes you should now have two custom implementations in your Shrio config; one of JdbcRealm overriding the "supports" method and one of CasRealm removing the authorization API methods.
There is one additional method based on explicitly declaring an Authorizer via Shiro's configuration that may be better suited to your situation.
Here is an explicit declaration of an Authorizer and Authenticator via a custom ShiroFilter extension. Both were implemented and registered to the provided JNDI names at startup.
public class CustomShiroFilter extends ShiroFilter {
#Override
public void init () throws Exception {
super.init();
DefaultWebSecurityManager dwsm = (DefaultWebSecurityManager) getSecurityManager();
dwsm.setAuthorizer((Authorizer)JndiUtil.get("realms/authorizerRealm"));
dwsm.setAuthenticator((Authenticator)JndiUtil.get("realms/authenticatorRealm"));
}
}
You need only one realm that extends AuthorizingRealm. It will provide
authc: method doGetAuthenticationInfo (CAS server)
authz: method doGetAuthorizationInfo (JDBC)
Hope this helps
We had a similar case where we use a LDAP Realm for authentication and used the standard shiro.ini file for the authorization for a simple use case.
To complement the answer of 'justin.hughey', I give the blueprint (could be spring as well) configuration in order to make your use case working:
<!-- Bean for Authentication -->
<bean id="rccadRealm" class="org.mydomain.myproject.security.shiro.ldap.realm.LdapRealm"
init-method="init">
<property name="searchBase" value="${realm.searchBase}" />
<property name="singleUserFilter" value="${realm.singleUserFilter}" />
<property name="timeout" value="30000" />
<property name="url" value="${contextFactory.url}" />
<property name="systemUsername" value="${contextFactory.systemUsername}" />
<property name="systemPassword" value="${contextFactory.systemPassword}" />
</bean>
<!-- Bean for Authorization -->
<bean id="iniRealm" class="org.mydomain.myproject.security.realm.AuthzOnlyIniRealm">
<argument value="file:$[config.base]/etc/shiro.ini"/>
<property name="authorizationCachingEnabled" value="true" />
</bean>
<bean id="myModularAuthenticator"
class="org.mydomain.myproject.security.service.MyModularRealmAuthenticator">
<property name="realms">
<list>
<ref component-id="ldapRealm" />
</list>
</property>
</bean>
<bean id="mySecurityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<property name="authenticator" ref="myModularAuthenticator" />
<property name="authorizer" ref="iniRealm" />
<property name="cacheManager" ref="cacheManager" />
</bean>
The key things is that we needed:
a modularRealmAuthenticator and let the default strategy (as there's only one realm) for the 'authenticator'
a special AuthzOnlyIniRealm which overrides the method supports returning false to prevent using it for authentication.
Our LdapRealm implementation is just an extension of the Shiro ActiveDirectoryRealm.

apache shiro: how to set the authenticationStrategy using spring applicationcontext?

I've been struggling with authenticationStrategy settings with shiro 1.2.1 in a spring based web application. I have 2 realms. One authenticates against database and one against ldap. both realms are working fine just that i wanted a FirstSuccessfulStrategy but it seems both realms are still being called. here is my security-application-context:
<bean id="passwordService" class="org.apache.shiro.authc.credential.DefaultPasswordService">
<property name="hashService" ref="hashService" />
</bean>
<bean id="hashService" class="org.apache.shiro.crypto.hash.DefaultHashService">
<property name="hashAlgorithmName" value="SHA-512" />
<property name="hashIterations" value="500000" />
</bean>
<bean id="SaltedSha512JPARealm" class="bla.bla.webapp.security.SaltedSha512JPARealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.PasswordMatcher">
<property name="passwordService" ref="passwordService"/>
</bean>
</property>
</bean>
<bean id="ldapContextFactory" class="org.apache.shiro.realm.ldap.JndiLdapContextFactory">
<property name="url" value="${user.ldap.connection.url}"/>
<property name="authenticationMechanism" value="${user.ldap.connection.auth_mecanism}"/>
</bean>
<bean id="ldapRealm" class="bla.bla.webapp.security.LDAPRealm">
<property name="userDnTemplate" value="${user.ldap.connection.userDnTemplate}"/>
<property name="contextFactory" ref="ldapContextFactory" />
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="roleRepository,roleRightRepository,rightRepository,userRepository">
<property name="realms">
<list>
<ref local="ldapRealm"/>
<ref local="SaltedSha512JPARealm"/>
</list>
</property>
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
</property>
</bean>
is there anything there that i am not doing well?
FirstSuccessfulStrategy means that your authenticator will try all your realms to authenticate user until the first successful. Your realms was configured in order: ldapRealm, SaltedSha512JPARealm. So if lapRealm will fail authenticator will try second one. To solve this you can try to configure the most successful or the quickest realm to be first, e.g. you can change your realms order to be SaltedSha512JPARealm, ldapRealm:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="roleRepository,roleRightRepository,rightRepository,userRepository">
<property name="realms">
<list>
<ref local="SaltedSha512JPARealm"/>
<ref local="ldapRealm"/>
</list>
</property>
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
</property>
</bean>
But you should understand that for this configuration if SaltedSha512JPARealm will fail, authenticator will try ldapRealm.
Or you can try to use different token classes for this realms. But it will work only if you have different authentication entry points for each of them.
UPD
It seems that ModularRealmAuthenticator is designed so that it will always try to authenticate user by all realms. FirstSuccessfulStrategy can affect only on authentication result. It will return first successful AuthenticationInfo. To achieve your goal you need to override ModularRealmAuthenticator#doMultiRealmAuthentication method. It can look like this:
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, t);
}
}
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
// dirty dirty hack
if (aggregate != null && !CollectionUtils.isEmpty(aggregate.getPrincipals())) {
return aggregate;
}
// end dirty dirty hack
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
</property>
the above definition is wrong. Define it as follows
<property name="authenticator.authenticationStrategy" ref="authcStrategy"/>
And define the below bean definition separately
<bean id="authcStrategy" class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
Then it will work as expected

Resources