I hope you don't mind me writing you directly. I am trying to implement
OpenID and Username/Password authentication. I read your great article at
T5
wiki.
My goal: authenticate via DaoAuthenticationProvider if user inputs
username
and password OR authenticate via OpenIDAuthenticationProvider if user
enters
openid url.
The problem: OpenIDAuthenticationProvider is trying to authenticate
eventhough it is contributed as second provider.
Can you please have a look at the AppModule code and suggest a correction?
AppModule.java
============
import java.io.IOException;
import
nu.localhost.tapestry5.springsecurity.services.SpringSecurityServices;
import
nu.localhost.tapestry5.springsecurity.services.internal.HttpServletRequestFilterWrapper;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Value;
import org.apache.tapestry5.services.HttpServletRequestFilter;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestFilter;
import org.apache.tapestry5.services.RequestHandler;
import org.apache.tapestry5.services.Response;
import org.slf4j.Logger;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.providers.AuthenticationProvider;
import org.springframework.security.providers.dao.SaltSource;
import org.springframework.security.providers.encoding.PasswordEncoder;
import
org.springframework.security.providers.openid.OpenIDAuthenticationProvider;
import
org.springframework.security.ui.openid.OpenIDAuthenticationProcessingFilter;
import org.springframework.security.ui.rememberme.RememberMeServices;
import org.springframework.security.userdetails.UserDetailsService;
/**
* This module is automatically included as part of the Tapestry IoC
Registry, it's a good place to configure and extend
* Tapestry, or to place your own service definitions.
*/
public class AppModule {
public static void bind(ServiceBinder binder) {
binder.bind(PersistenceManager.class,
PersistenceManagerImpl.class);
}
public static void
contributeApplicationDefaults(MappedConfiguration<String, String>
configuration) {
configuration.add(SymbolConstants.SUPPORTED_LOCALES,
"sl_SI,sr,en");
// The factory default is true but during the early stages of an
application
// overriding to false is a good idea. In addition, this is often
overridden
// on the command line as -Dtapestry.production-mode=false
configuration.add(SymbolConstants.PRODUCTION_MODE, "false");
configuration.add(SymbolConstants.COMPRESS_WHITESPACE, "false");
configuration.add("spring-security.failure.url", "/login/failed");
// configuration.add( "spring-security.accessDenied.url",
"/forbidden" );
// configuration.add(
// "spring-security.check.url",
// "/j_spring_security_check" );
configuration.add("spring-security.target.url", "/index");
// configuration.add( "spring-security.afterlogout.url", "/" );
// configuration.add( "spring-security.rememberme.key",
"REMEMBERMEKEY" );
configuration.add("spring-security.loginform.url", "/login");
// configuration.add( "spring-security.force.ssl.login", "false" );
// configuration.add( "spring-security.anonymous.key",
"acegi_anonymous" );
// configuration.add(
// "spring-security.anonymous.attribute",
// "anonymous,ROLE_ANONYMOUS" );
configuration.add( "spring-security.password.salt", "DEADBEEF" );
}
/**
* This is a service definition, the service will be named
"TimingFilter". The interface, RequestFilter, is used
* within the RequestHandler service pipeline, which is built from the
RequestHandler service configuration.
* Tapestry IoC is responsible for passing in an appropriate Logger
instance. Requests for static resources are
* handled at a higher level, so this filter will only be invoked for
Tapestry related requests.
*
* <p>
* Service builder methods are useful when the implementation is inline
as an inner class (as here) or require some
* other kind of special initialization. In most cases, use the static
bind() method instead.
*
* <p>
* If this method was named "build", then the service id would be taken
from the service interface and would be
* "RequestFilter". Since Tapestry already defines a service named
"RequestFilter" we use an explicit service id
* that we can reference inside the contribution method.
*/
public RequestFilter buildTimingFilter(final Logger log) {
return new RequestFilter() {
public boolean service(Request request, Response response,
RequestHandler handler) throws IOException {
long startTime = System.currentTimeMillis();
try {
// The responsibility of a filter is to invoke the
corresponding method
// in the handler. When you chain multiple filters
together, each filter
// received a handler that is a bridge to the next
filter.
return handler.service(request, response);
} finally {
long elapsed = System.currentTimeMillis() - startTime;
log.info(String.format("Request time: %d ms",
elapsed));
}
}
};
}
/**
* This is a contribution to the RequestHandler service configuration.
This is how we extend Tapestry using the
* timing filter. A common use for this kind of filter is transaction
management or security. The @Local annotation
* selects the desired service by type, but only from the same module.
Without @Local, there would be an error due
* to the other service(s) that implement RequestFilter (defined in
other modules).
*/
public void
contributeRequestHandler(OrderedConfiguration<RequestFilter>
configuration, @Local RequestFilter filter) {
// Each contribution to an ordered configuration has a name, When
necessary, you may
// set constraints to precisely control the invocation order of the
contributed filter
// within the pipeline.
configuration.add("Timing", filter);
}
/* COMMON UserDetailsService */
public static UserDetailsService buildUserDetailsService(@Inject
PersistenceManager persistenceManager, final Logger log) {
return new UserDetailsServiceImpl(persistenceManager,log);
}
/* USERNAME, PASSWORD */
// public static UserDetailsService
buildUserDetailsWithUsernameAndPasswordService(/*...@inject PasswordEncoder
encoder,
// @Inject SaltSource salt, */final Logger log) {
// return new UserDetailsWithUsernameAndPasswordService(/*encoder,
salt, */log);
// }
/* OPENID */
// public static UserDetailsService buildUserDetailsWithOpenIDService()
{
// return new UserDetailsWithOpenIDServiceImpl();
// }
public static OpenIDAuthenticationProvider
buildOpenIDAuthenticationProvider(
@InjectService("UserDetailsWithOpenIDService")
UserDetailsService userDetailsService) throws Exception {
OpenIDAuthenticationProvider provider = new
OpenIDAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.afterPropertiesSet();
return provider;
}
public static void
contributeProviderManager(OrderedConfiguration<AuthenticationProvider>
configuration,
@InjectService("DaoAuthenticationProvider")
AuthenticationProvider daoAuthenticationProvider,
@InjectService("OpenIDAuthenticationProvider")
AuthenticationProvider openIdAuthenticationProvider) {
configuration.add("daoAuthenticationProvider",
daoAuthenticationProvider);
configuration.add("openIDAuthenticationProvider",
openIdAuthenticationProvider);
}
public static OpenIDAuthenticationProcessingFilter
buildRealOpenIDAuthenticationProcessingFilter(
@SpringSecurityServices final AuthenticationManager manager,
@SpringSecurityServices final RememberMeServices
rememberMeServices,
@Inject @Value("${spring-security.check.url}") final String
authUrl,
@Inject @Value("${spring-security.target.url}") final String
targetUrl,
@Inject @Value("${spring-security.failure.url}") final String
failureUrl) throws Exception {
OpenIDAuthenticationProcessingFilter filter = new
OpenIDAuthenticationProcessingFilter();
filter.setAuthenticationManager(manager);
filter.setAuthenticationFailureUrl(failureUrl);
filter.setDefaultTargetUrl(targetUrl);
filter.setFilterProcessesUrl(authUrl);
filter.setRememberMeServices(rememberMeServices);
filter.afterPropertiesSet();
return filter;
}
public static HttpServletRequestFilter
buildOpenIDAuthenticationProcessingFilter(
final OpenIDAuthenticationProcessingFilter filter) {
return new HttpServletRequestFilterWrapper(filter);
}
public static void contributeHttpServletRequestHandler(
OrderedConfiguration<HttpServletRequestFilter> configuration,
@InjectService("OpenIDAuthenticationProcessingFilter")
HttpServletRequestFilter openIDAuthenticationProcessingFilter) {
configuration.add("openIDAuthenticationProcessingFilter",
openIDAuthenticationProcessingFilter,
"before:springSecurityAuthenticationProcessingFilter",
"after:springSecurityHttpSessionContextIntegrationFilter");
}
}
Is there a problem in this last method
("before:springSecurityAuthenticationProcessingFilter")? Both forms (for
u/p
and openid url) are on the same page.
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>Najdi.si prijavna stran</title>
<link rel="stylesheet" type="text/css"
href="${asset:context:css/iopenid.css}" />
</head>
<body>
<t:layout xmlns:t="
http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<div style="margin-left: 50px">
<t:block t:id="loginWithUserNameAndPassword">
<p>
Prijavite se z uporabniškim imenom in geslom ali z
<a t:type="actionlink" t:id="refreshOpenIDZone"
href="#" t:zone="loginZone">OpenID</a>
</p>
<form xmlns:t="
http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"
action="${loginCheckUrl}" method="POST">
<t:if test="failed">
Napačno uporabniško ime ali geslo!
<br />
</t:if>
<label class="username"
for="j_username">uporabniško
ime:</label>
<input class="username" name="j_username"
type="text" size="10" maxlength="30" />
<label class="password"
for="j_password">geslo:</label>
<input class="password" name="j_password"
type="password" size="10" maxlength="30" />
<input id="submit" class="submit" type="submit"
value="log in" />
</form>
<p>Še nimate Najdi.si računa? <a t:type="pagelink"
t:page="Register" href="#">Registrirajte se!</a></p>
<p><a t:type="pagelink" t:page="RetrievePassword"
href="#">Pozabil sem geslo.</a></p>
</t:block>
<t:block t:id="loginWithOpenID">
<p>
Prijavite se z
<a t:type="actionlink"
t:id="refreshUsernamePasswordZone" href="#"
t:zone="loginZone">uporabniškim
imenom in
geslom</a>
ali z OpenID
</p>
<form xmlns:t="
http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"
action="${loginCheckUrl}" method="POST">
<t:if test="failed">
Napaka!
<br />
</t:if>
<label class="username"
for="j_username">OpenID:</label>
<input class="username" name="j_username"
type="text" size="30" />
<input id="submit" class="submit" type="submit"
value="log in" />
</form>
<p>Še nimate Najdi.si računa? Ob <a t:type="pagelink"
t:page="Register" href="#">registraciji</a> dobite tudi Najdi.si
OpenID.</p>
</t:block>
<t:zone t:id="loginZone">
<t:delegate to="loginWithUserNameAndPassword" />
</t:zone>
<br />
</div>
</t:layout>
</body>
</html>
public class Login {
@Inject
@Property
private Block loginWithUserNameAndPassword;
@Inject
@Property
private Block loginWithOpenID;
@Inject
@Property
private Request _request;
void onActionFromRefreshPage() {
// Nothing to do - the page will get fresh times as it is rendered.
}
Block onActionFromRefreshOpenIDZone() {
// Check this is an AJAX link - if the link is clicked before the
DOM is fully loaded then AJAX behaviour won't
// be there so don't try returning a Block. See
https://issues.apache.org/jira/browse/TAP5-1 .
if (!_request.isXHR()) {
return null;
}
// Return the zone we want rendered. Without Ajax we'd typically
return the page we want rendered.
return loginWithOpenID;
}
Block onActionFromRefreshUsernamePasswordZone() {
// Check this is an AJAX link - if the link is clicked before the
DOM is fully loaded then AJAX behaviour won't
// be there so don't try returning a Block. See
https://issues.apache.org/jira/browse/TAP5-1 .
if (!_request.isXHR()) {
return null;
}
// Return the zone we want rendered. Without Ajax we'd typically
return the page we want rendered.
return loginWithUserNameAndPassword;
}
@Inject
@Value("${spring-security.check.url}")
private String checkUrl;
@Inject
private Request request;
private boolean failed = false;
public boolean isFailed() {
return failed;
}
public String getLoginCheckUrl() {
return request.getContextPath() + checkUrl;
}
void onActivate(String extra) {
if (extra.equals("failed")) {
failed = true;
}
}
I would be really gratefull for your help!
Regards,
Borut