Search code examples
javainversion-of-controltapestrytynamo

Tapestry override Authenticator


I am trying to use a custom authenticator for tapestry-security (org.tynamo.security).

I have a custom authenticator

public class EnvironmentalRealmAuthenticator extends ModularRealmAuthenticator

And in my module I override the default authenticator of Tapestry (ModularRealmAuthenticator):

public static void bind(final ServiceBinder binder) {

    binder.bind(Authenticator.class, EnvironmentalRealmAuthenticator.class).withId("EnvironmentalRealmAuthenticator");
}

@Contribute(ServiceOverride.class)
public static void setupOverrides(final MappedConfiguration<Class, Object> configuration, @Local final Authenticator override) {
    configuration.add(Authenticator.class, override);
}

However, this causes the cache to not be cleared on logout - I have the suspicion that this is caused by the way the DefaultSecurityManager of Shiro detects if the authenticator listens to logouts:

Authenticator authc = getAuthenticator();
if (authc instanceof LogoutAware) {
    ((LogoutAware) authc).onLogout(principals);
}

As the EnvironmentalRealmAuthenticator is bound as a Tapestry service, it is initially injected as a proxy and hence authc instanceof LogoutAware yields false - that's why the default ModularRealmAuthenticator is bound in a different way in the SecurityModule of Tynamo:

// TYNAMO-155 It's not enough to identify ModularRealmAuthenticator by it's Authenticator interface only
// because Shiro tests if the object is an instanceof LogoutAware to call logout handlers
binder.bind(ModularRealmAuthenticator.class);

However, when I try to override my EnvironmentalRealmAuthenticator that way

binder.bind(EnvironmentalRealmAuthenticator.class).withId("EnvironmentalRealmAuthenticator");

this results in the following exception:

Caused by: java.lang.IllegalStateException: Construction of service 'ServiceOverride' has failed due to recursion: the service depends on itself in some way. Please check org.apache.tapestry5.ioc.internal.services.ServiceOverrideImpl(Map) (at ServiceOverrideImpl.java:31) via org.apache.tapestry5.ioc.modules.TapestryIOCModule.bind(ServiceBinder) (at TapestryIOCModule.java:52) for references to another service that is itself dependent on service 'ServiceOverride'.


Solution

  • I seem to have found a (rather hacky) way. Instead of overriding the Authenticator itself, I override the WebSecuritymanager instead:

    public static void bind(final ServiceBinder binder) {
        binder.bind(EnvironmentalRealmAuthenticator.class).withId("EnvironmentalRealmAuthenticator");
        binder.bind(WebSecurityManager.class, EnvironmentalSecurityManager.class).withId("EnvironmentalSecurityManager");
    }
    
    @Contribute(ServiceOverride.class)
    public static void setupOverrides(final MappedConfiguration<Class, Object> configuration, @Local final WebSecurityManager override) {
        configuration.add(WebSecurityManager.class, override);
    }
    

    That way I don't have to bind the EnvironmentalRealmAuthenticator with its interface. In order to be able to identify the new Authenticator I annotated the module:

    @Marker(Primary.class)
    

    The implmentation of the EnvironmentalSecurityManager then looks like this:

    /**
     * Used to properly (and uniquely) identify the authenticator (without having to override it)
     */
    public class EnvironmentalSecurityManager extends TapestryRealmSecurityManager {
    
        private final Logger logger = LoggerFactory.getLogger(EnvironmentalSecurityManager.class);
    
        /**
         * Mind the @Primary annotation, used to identify the EnvironmentalRealmAuthenticator
         */
        public EnvironmentalSecurityManager(final @Primary Authenticator authenticator, final SubjectFactory subjectFactory, final RememberMeManager rememberMeManager, final Collection<Realm> realms) {
    
            super(authenticator, subjectFactory, rememberMeManager, realms);
            logger.debug("Created EnvironmentalSecurityManager - class of authenticator is {}", authenticator.getClass());
        }
    }
    

    That way I can guarantee that the correct Authenticator is used without actually having to override it.