Archive for the ‘- Spring framework /              ZK integration’ Category.

How to customize a spring-security form-login. Part 5

Coming soon…

Samples are hostet in the Zksample2 project on

How to customize a spring-security form-login. Part 4

An article from my zk integration series.

part 1
part 2
part 3
part 5

AuthenticationSuccessHandler.java
Handles the workflow after a successful authentication. It extends from SimpleUrlAuthenticationSuccessHandler in which we can configure a default URL. There the successful authenticated users can be sent to.

AuthenticationSuccessHandler.java

public class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

	private transient static final Logger logger = Logger.getLogger(MyAuthenticationSuccessHandler.class);

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("MyAuthenticationSuccessHandler");
			logger.debug(request.getContextPath());
			logger.debug(getDefaultTargetUrl());
			logger.debug(request.getContextPath() + getDefaultTargetUrl());
			logger.debug("getPrincipal() : " + authentication.getPrincipal().toString());
			logger.debug("getName() : " + authentication.getName());
			logger.debug("getCredentials() : " + authentication.getCredentials());

			Collection<GrantedAuthority> col = authentication.getAuthorities();

			logger.debug("getAuthorities() COUNT : " + col.size());
			int i = 0;
			for (GrantedAuthority grantedAuthority : col) {
				++i;
				logger.debug("getAuthorities()" + i + " : " + grantedAuthority.getAuthority().toString());
			}
			logger.debug("isAuthenticated : " + authentication.isAuthenticated());
		}

		// redirect the user to the configured URL
		response.sendRedirect(request.getContextPath() + getDefaultTargetUrl());
	}
}

Part 5 of this article you can read here.

Samples are hostet in the Zksample2 project on

Have fun with it.

Stephan Gerth

Dipl.rer.pol.


PS: Help to prevent the global warming by writing cool software

How to customize a spring-security form-login. Part 3

An article from my zk integration series.

part 1
part 2
part 4
part 5

AbstractUserDetailsAuthenticationProvider.java
The method additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordTenantAuthenticationToken authentication) allow us additional checks for a returned UserDetails. We adapt it only to our UsernamePasswordTenantAuthenticationToken.

AbstractUserDetailsAuthenticationProvider.java

public abstract class MyAbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
	// ~ Instance fields
	// ================================================================================================

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private UserCache userCache = new NullUserCache();
	private boolean forcePrincipalAsString = false;
	protected boolean hideUserNotFoundExceptions = true;
	private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
	private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();

	// ~ Methods
	// ========================================================================================================

	/**
	 * Allows subclasses to perform any additional checks of a returned (or
	 * cached) <code>UserDetails</code> for a given authentication request.
	 * Generally a subclass will at least compare the
	 * {@link Authentication#getCredentials()} with a
	 * {@link UserDetails#getPassword()}. If custom logic is needed to compare
	 * additional properties of <code>UserDetails</code> and/or
	 * <code>UsernamePasswordAuthenticationToken</code>, these should also
	 * appear in this method.
	 * 
	 * @param userDetails
	 *            as retrieved from the
	 *            {@link #retrieveUser(String, UsernamePasswordAuthenticationToken)}
	 *            or <code>UserCache</code>
	 * @param authentication
	 *            the current request that needs to be authenticated
	 * 
	 * @throws AuthenticationException
	 *             AuthenticationException if the credentials could not be
	 *             validated (generally a <code>BadCredentialsException</code>,
	 *             an <code>AuthenticationServiceException</code>)
	 */
	protected abstract void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordTenantAuthenticationToken authentication) throws AuthenticationException;

	public final void afterPropertiesSet() throws Exception {
		Assert.notNull(this.userCache, "A user cache must be set");
		Assert.notNull(this.messages, "A message source must be set");
		doAfterPropertiesSet();
	}

	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordTenantAuthenticationToken.class, authentication, messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
				"Only UsernamePasswordTenantAuthenticationToken is supported"));

		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
				user = retrieveUser(username, (UsernamePasswordTenantAuthenticationToken) authentication);
			} catch (UsernameNotFoundException notFound) {
				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
				} else {
					throw notFound;
				}
			}

			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}

		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordTenantAuthenticationToken) authentication);
		} catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username, (UsernamePasswordTenantAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user, (UsernamePasswordTenantAuthenticationToken) authentication);
			} else {
				throw exception;
			}
		}

		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

	/**
	 * Creates a successful {@link Authentication} object.
	 * <p>
	 * Protected so subclasses can override.
	 * </p>
	 * <p>
	 * Subclasses will usually store the original credentials the user supplied
	 * (not salted or encoded passwords) in the returned
	 * <code>Authentication</code> object.
	 * </p>
	 * 
	 * @param principal
	 *            that should be the principal in the returned object (defined
	 *            by the {@link #isForcePrincipalAsString()} method)
	 * @param authentication
	 *            that was presented to the provider for validation
	 * @param user
	 *            that was loaded by the implementation
	 * 
	 * @return the successful authentication token
	 */
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
		// Ensure we return the original credentials the user supplied,
		// so subsequent attempts are successful even with encoded passwords.
		// Also ensure we return the original getDetails(), so that future
		// authentication events after cache expiry contain the details

		// OLD
		// UsernamePasswordTenantAuthenticationToken result = new
		// UsernamePasswordTenantAuthenticationToken(principal,
		// authentication.getCredentials(), user.getAuthorities());
		UsernamePasswordTenantAuthenticationToken result = new UsernamePasswordTenantAuthenticationToken(principal, authentication.getCredentials(), user.getAuthorities(),
				((UsernamePasswordTenantAuthenticationToken) authentication).getTenantId());
		result.setDetails(authentication.getDetails());

		return result;
	}

	protected void doAfterPropertiesSet() throws Exception {
	}

	public UserCache getUserCache() {
		return userCache;
	}

	public boolean isForcePrincipalAsString() {
		return forcePrincipalAsString;
	}

	public boolean isHideUserNotFoundExceptions() {
		return hideUserNotFoundExceptions;
	}

	/**
	 * Allows subclasses to actually retrieve the <code>UserDetails</code> from
	 * an implementation-specific location, with the option of throwing an
	 * <code>AuthenticationException</code> immediately if the presented
	 * credentials are incorrect (this is especially useful if it is necessary
	 * to bind to a resource as the user in order to obtain or generate a
	 * <code>UserDetails</code>).
	 * <p>
	 * Subclasses are not required to perform any caching, as the
	 * <code>AbstractUserDetailsAuthenticationProvider</code> will by default
	 * cache the <code>UserDetails</code>. The caching of
	 * <code>UserDetails</code> does present additional complexity as this means
	 * subsequent requests that rely on the cache will need to still have their
	 * credentials validated, even if the correctness of credentials was assured
	 * by subclasses adopting a binding-based strategy in this method.
	 * Accordingly it is important that subclasses either disable caching (if
	 * they want to ensure that this method is the only method that is capable
	 * of authenticating a request, as no <code>UserDetails</code> will ever be
	 * cached) or ensure subclasses implement
	 * {@link #additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)}
	 * to compare the credentials of a cached <code>UserDetails</code> with
	 * subsequent authentication requests.
	 * </p>
	 * <p>
	 * Most of the time subclasses will not perform credentials inspection in
	 * this method, instead performing it in
	 * {@link #additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)}
	 * so that code related to credentials validation need not be duplicated
	 * across two methods.
	 * </p>
	 * 
	 * @param username
	 *            The username to retrieve
	 * @param authentication
	 *            The authentication request, which subclasses <em>may</em> need
	 *            to perform a binding-based retrieval of the
	 *            <code>UserDetails</code>
	 * 
	 * @return the user information (never <code>null</code> - instead an
	 *         exception should the thrown)
	 * 
	 * @throws AuthenticationException
	 *             if the credentials could not be validated (generally a
	 *             <code>BadCredentialsException</code>, an
	 *             <code>AuthenticationServiceException</code> or
	 *             <code>UsernameNotFoundException</code>)
	 */
	protected abstract UserDetails retrieveUser(String username, UsernamePasswordTenantAuthenticationToken authentication) throws AuthenticationException;

	public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
		this.forcePrincipalAsString = forcePrincipalAsString;
	}

	/**
	 * By default the <code>AbstractUserDetailsAuthenticationProvider</code>
	 * throws a <code>BadCredentialsException</code> if a username is not found
	 * or the password is incorrect. Setting this property to <code>false</code>
	 * will cause <code>UsernameNotFoundException</code>s to be thrown instead
	 * for the former. Note this is considered less secure than throwing
	 * <code>BadCredentialsException</code> for both exceptions.
	 * 
	 * @param hideUserNotFoundExceptions
	 *            set to <code>false</code> if you wish
	 *            <code>UsernameNotFoundException</code>s to be thrown instead
	 *            of the non-specific <code>BadCredentialsException</code>
	 *            (defaults to <code>true</code>)
	 */
	public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
		this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public void setUserCache(UserCache userCache) {
		this.userCache = userCache;
	}

	public boolean supports(Class<? extends Object> authentication) {
		return (UsernamePasswordTenantAuthenticationToken.class.isAssignableFrom(authentication));
	}

	protected UserDetailsChecker getPreAuthenticationChecks() {
		return preAuthenticationChecks;
	}

	/**
	 * Sets the policy will be used to verify the status of the loaded
	 * <tt>UserDetails</tt> <em>before</em> validation of the credentials takes
	 * place.
	 * 
	 * @param preAuthenticationChecks
	 *            strategy to be invoked prior to authentication.
	 */
	public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
		this.preAuthenticationChecks = preAuthenticationChecks;
	}

	protected UserDetailsChecker getPostAuthenticationChecks() {
		return postAuthenticationChecks;
	}

	public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
		this.postAuthenticationChecks = postAuthenticationChecks;
	}

	private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
		public void check(UserDetails user) {
			if (!user.isAccountNonLocked()) {
				throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), user);
			}

			if (!user.isEnabled()) {
				throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"), user);
			}

			if (!user.isAccountNonExpired()) {
				throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), user);
			}
		}
	}

	private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
		public void check(UserDetails user) {
			if (!user.isCredentialsNonExpired()) {
				throw new CredentialsExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"), user);
			}
		}
	}
}

MyDaoAuthenticationProvider.java
An implementation of an AuthenticationProvider that retrieves the user details from an UserDetailsService.

MyDaoAuthenticationProvider .java

public class MyDaoAuthenticationProvider extends MyAbstractUserDetailsAuthenticationProvider {

	// ~ Instance fields
	// ================================================================================================

	private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();

	private SaltSource saltSource;

	private UserDetailsService userDetailsService;

	private boolean includeDetailsObject = true;

	// ~ Methods
	// ========================================================================================================

	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordTenantAuthenticationToken authentication) throws AuthenticationException {
		Object salt = null;

		if (this.saltSource != null) {
			salt = this.saltSource.getSalt(userDetails);
		}

		if (authentication.getCredentials() == null) {
			throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), includeDetailsObject ? userDetails : null);
		}

		String presentedPassword = authentication.getCredentials().toString();

		if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
			throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), includeDetailsObject ? userDetails : null);
		}
	}

	protected void doAfterPropertiesSet() throws Exception {
		Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
	}

	protected final UserDetails retrieveUser(String username, UsernamePasswordTenantAuthenticationToken authentication) throws AuthenticationException {
		UserDetails loadedUser;

		try {
			loadedUser = this.getUserDetailsService().loadUserByUsername(username + "/" + authentication.getTenantId());
		} catch (DataAccessException repositoryProblem) {
			throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
		}

		if (loadedUser == null) {
			throw new AuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
		}
		return loadedUser;
	}

	/**
	 * Sets the PasswordEncoder instance to be used to encode and validate
	 * passwords. If not set, {@link PlaintextPasswordEncoder} will be used by
	 * default.
	 * 
	 * @param passwordEncoder
	 *            The passwordEncoder to use
	 */
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	protected PasswordEncoder getPasswordEncoder() {
		return passwordEncoder;
	}

	/**
	 * The source of salts to use when decoding passwords. <code>null</code> is
	 * a valid value, meaning the <code>DaoAuthenticationProvider</code> will
	 * present <code>null</code> to the relevant <code>PasswordEncoder</code>.
	 * 
	 * @param saltSource
	 *            to use when attempting to decode passwords via the
	 *            <code>PasswordEncoder</code>
	 */
	public void setSaltSource(SaltSource saltSource) {
		this.saltSource = saltSource;
	}

	protected SaltSource getSaltSource() {
		return saltSource;
	}

	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	protected UserDetailsService getUserDetailsService() {
		return userDetailsService;
	}

	protected boolean isIncludeDetailsObject() {
		return includeDetailsObject;
	}

	/**
	 * Determines whether the UserDetails will be included in the
	 * <tt>extraInformation</tt> field of a thrown BadCredentialsException.
	 * Defaults to true, but can be set to false if the exception will be used
	 * with a remoting protocol, for example.
	 * 
	 * @deprecated use
	 *             {@link org.springframework.security.authentication.ProviderManager#setClearExtraInformation(boolean)}
	 */
	public void setIncludeDetailsObject(boolean includeDetailsObject) {
		this.includeDetailsObject = includeDetailsObject;
	}
}

Part 4 of this article you can read here.

Samples are hostet in the Zksample2 project on

Have fun with it.

Stephan Gerth

Dipl.rer.pol.


PS: Help to prevent the global warming by writing cool software

How to customize a spring-security form-login. Part 2

An article from my zk integration series.

part 1
part 3
part 4
part 5

Bellow are the used main classes for a spring-security form-login:

UsernamePasswordAuthenticationToken.java:
This class is an authentication implementation that represents a simple username and password. We modifiy it for the TenantId and change the name to UsernamePasswordTenantAuthenticationToken.java

UsernamePasswordTenantAuthenticationToken.java

@SuppressWarnings("serial")
public class UsernamePasswordTenantAuthenticationToken extends AbstractAuthenticationToken {
	// ~ Instance fields
	// ================================================================================================

	private final Object credentials;
	private final Object principal;
	private final Object tenantId;

	// ~ Constructors
	// ===================================================================================================

	/**
	 * This constructor can be safely used by any code that wishes to create a
	 * <code>UsernamePasswordAuthenticationToken</code>, as the
	 * {@link #isAuthenticated()} will return <code>false</code>.
	 * 
	 */
	public UsernamePasswordTenantAuthenticationToken(Object principal, Object credentials, Object tenantId) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		this.tenantId = tenantId;
		setAuthenticated(false);
	}

	/**
	 * @deprecated use the list of authorities version
	 */
	public UsernamePasswordTenantAuthenticationToken(Object principal, Object credentials, GrantedAuthority[] authorities) {
		this(principal, credentials, Arrays.asList(authorities));
	}

	/**
	 * This constructor should only be used by
	 * <code>AuthenticationManager</code> or <code>AuthenticationProvider</code>
	 * implementations that are satisfied with producing a trusted (i.e.
	 * {@link #isAuthenticated()} = <code>true</code>) authentication token.
	 * 
	 * @param principal
	 * @param credentials
	 * @param authorities
	 */
	public UsernamePasswordTenantAuthenticationToken(Object principal, Object credentials, Collection<GrantedAuthority> authorities, Object tenantId) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		this.tenantId = tenantId;
		super.setAuthenticated(true); // must use super, as we override
	}

	// ~ Methods
	// ========================================================================================================

	public Object getCredentials() {
		return this.credentials;
	}

	public Object getPrincipal() {
		return this.principal;
	}

	public Object getTenantId() {
		return this.tenantId;
	}

	public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
		if (isAuthenticated) {
			throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
		}

		super.setAuthenticated(false);
	}
}

UserPasswordAuthenticationFilter.java
This class processes the submission of an authentication form, means our ZKLoginDialog . This filter responds per default to the /j_spring_security_check URL.

UserPasswordAuthenticationFilter.java

public class UserPasswordTenantAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	// ~ Static fields/initializers
	// =====================================================================================

	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password";
	public static final String SPRING_SECURITY_FORM_TENANTID_KEY = "j_tenantid";
	public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME";

	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
	private String tenantParameter = SPRING_SECURITY_FORM_TENANTID_KEY;
	private boolean postOnly = true;

	// ~ Constructors
	// ===================================================================================================

	public UserPasswordTenantAuthenticationFilter() {
		super("/j_spring_security_check");
	}

	// ~ Methods
	// ========================================================================================================

	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);
		String tenantId = obtainTenantId(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		if (tenantId == null) {
			tenantId = "";
		}

		username = username.trim();
		tenantId = tenantId.trim();

		UsernamePasswordTenantAuthenticationToken authRequest = new UsernamePasswordTenantAuthenticationToken(username, password, null, tenantId);

		// Place the last username attempted into HttpSession for views
		HttpSession session = request.getSession(false);

		if (session != null || getAllowSessionCreation()) {
			request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));
		}

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);
	}

	/**
	 * Enables subclasses to override the composition of the password, such as
	 * by including additional values and a separator.
	 * <p>
	 * This might be used for example if a postcode/zipcode was required in
	 * addition to the password. A delimiter such as a pipe (|) should be used
	 * to separate the password and extended value(s). The
	 * <code>AuthenticationDao</code> will need to generate the expected
	 * password in a corresponding manner.
	 * </p>
	 * 
	 * @param request
	 *            so that request attributes can be retrieved
	 * 
	 * @return the password that will be presented in the
	 *         <code>Authentication</code> request token to the
	 *         <code>AuthenticationManager</code>
	 */
	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}

	/**
	 * Enables subclasses to override the composition of the username, such as
	 * by including additional values and a separator.
	 * 
	 * @param request
	 *            so that request attributes can be retrieved
	 * 
	 * @return the username that will be presented in the
	 *         <code>Authentication</code> request token to the
	 *         <code>AuthenticationManager</code>
	 */
	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}

	/**
	 * Enables subclasses to override the composition of the tenantID, such as
	 * by including additional values and a separator.
	 * 
	 * @param request
	 *            so that request attributes can be retrieved
	 * 
	 * @return the tenantID that will be presented in the
	 *         <code>Authentication</code> request token to the
	 *         <code>AuthenticationManager</code>
	 */
	protected String obtainTenantId(HttpServletRequest request) {
		return request.getParameter(tenantParameter);
	}

	/**
	 * Provided so that subclasses may configure what is put into the
	 * authentication request's details property.
	 * 
	 * @param request
	 *            that an authentication request is being created for
	 * @param authRequest
	 *            the authentication request object that should have its details
	 *            set
	 */
	protected void setDetails(HttpServletRequest request, UsernamePasswordTenantAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	/**
	 * Sets the parameter name which will be used to obtain the username from
	 * the login request.
	 * 
	 * @param usernameParameter
	 *            the parameter name. Defaults to "j_username".
	 */
	public void setUsernameParameter(String usernameParameter) {
		Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
		this.usernameParameter = usernameParameter;
	}

	/**
	 * Sets the parameter name which will be used to obtain the password from
	 * the login request..
	 * 
	 * @param passwordParameter
	 *            the parameter name. Defaults to "j_password".
	 */
	public void setPasswordParameter(String passwordParameter) {
		Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
		this.passwordParameter = passwordParameter;
	}

	/**
	 * Sets the parameter name which will be used to obtain the password from
	 * the login request..
	 * 
	 * @param passwordParameter
	 *            the parameter name. Defaults to "j_password".
	 */
	public void setTenantIdParameter(String tenantParameter) {
		Assert.hasText(tenantParameter, "Tenant ID parameter must not be empty or null");
		this.tenantParameter = tenantParameter;
	}

	/**
	 * Defines whether only HTTP POST requests will be allowed by this filter.
	 * If set to true, and an authentication request is received which is not a
	 * POST request, an exception will be raised immediately and authentication
	 * will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
	 * will be called as if handling a failed authentication.
	 * <p>
	 * Defaults to <tt>true</tt> but may be overridden by subclasses.
	 */
	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public final String getUsernameParameter() {
		return usernameParameter;
	}

	public final String getPasswordParameter() {
		return passwordParameter;
	}

	public final String getTenantParameter() {
		return tenantParameter;
	}
}

Part 3 of this article you can read here.

Samples are hostet in the Zksample2 project on

Have fun with it.

Stephan Gerth

Dipl.rer.pol.


PS: Help to prevent the global warming by writing cool software

How to customize a spring-security form-login. Part 1

An article from my zk integration series.

part 2
part 3
part 4
part 5

For a spring-security 3.0.2 managed project i need a third parameter in the login dialog. This third parameter should represent a tenant id for a multi-tenant application. The logic should lookup in the tenant administrations database for the used table schema name in which the tenants data are stored. Let me early say that the best practice not only for multi-tenant database applications is the way to separate completely the user/tenant and right data from the applications data. This separation is a basic necessity for high scaling applications. In doing so it’s equal if the users data are stored in a LDAP or relational database server. Mostly an LDAP server is prefered because it’s optimized for fast reading, means 1000’s of user entries in a second.

Here’s an picture on how the login dialog should looks like:

ZKLoginDialog

customized spring-security form-login dialog with zk framework

zkloginDialog.zul

<?page id="ZKLoginDialog" title="LOGIN"?>
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>

<zk xmlns="http://www.zkoss.org/2005/zul"
	xmlns:h="http://www.w3.org/1999/xhtml"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.zkoss.org/2005/zul http://www.zkoss.org/2005/zul/zul.xsd">

	<style>
		body { padding: 0 0; /* 0 padding on top and bottom and 0
		padding on right and left */

		<!-- background image -->
		background-image:
		url(${c:encodeURL('/images/templates/test/Grey_T_R.jpg')});
		background-repeat:repeat-x; }

		<!-- cut the vertical borders in the rows -->
		tr.z-row td.z-row-inner { border-right: 0px #CCC; }

		<!-- nicer looking -->
		.z-window-modal-cnt-noborder {background: none}
		.z-window-modal-cl-noborder {background: none}

		.outerGroupBox .z-groupbox-cnt {padding: 0px;}

		<!-- Make Plain Grid -->
		.GridLayoutNoBorder tr.z-row td.z-row-inner, tr.z-row
		.z-cell,div.z-grid { border: none; overflow: hidden; zoom: 1;
		background: white; border-top: none; border-left: none;
		border-right: none; border-bottom: none; }
	</style>

	<window id="loginwin" border="none" width="390px"
		apply="de.forsthaus.webui.login.ZkLoginDialogCtrl">

		<groupbox mold="3d" closable="false" sclass="outerGroupBox"
			contentStyle="background-color : white">
			<caption label="Login" image="/images/earth2.gif"
				style="font-weight: bold;">

				<button id="button_ZKLoginDialog_Close"
					image="/images/icons/stop.gif"
					tooltiptext="${c:l('button_ZKLoginDialog_Close.tooltiptext')}" />
			</caption>

			<columnlayout
				style="background-image:url(${c:encodeURL('/images/login_151x222.jpg')}); ">
				<columnchildren width="30%" />

				<columnchildren width="70%">
					<panel border="none">
						<panelchildren>
							<separator />
							<groupbox
								if="${not empty param.login_error}">
								<label style="color:red"
									value="Login failed. Please try again." />
								<h:br />
								<label style="color:red"
									value="Reason: ${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message}" />
								<h:br />
							</groupbox>

							<groupbox
								style="padding-top: 5px; padding-left: 5px; ">
								<caption label="Login" />

								<div>
									<!-- ### Spring Security action-url = j_spring_security_check  ### -->
									<h:form id="f" name="f"
										action="j_spring_security_check" method="POST">

										<grid fixedLayout="true"
											sclass="GridLayoutNoBorder" style="border:0px">
											<columns>
												<column width="40%" />
												<column width="60%" />
											</columns>
											<rows>

												<!-- TenantID -->
												<row>
													<label
														id="label_ZKLoginDialog_tenant"
														value="${c:l('label_ZKLoginDialogTenant')}" />
													<textbox
														id="txtb_TenantId" name="j_tenantid" width="98%" />
												</row>

												<!-- Username -->
												<row>
													<label
														id="label_ZKLoginDialog_user"
														value="${c:l('label_ZKLoginDialogUser')}" />
													<textbox
														id="txtb_Username" name="j_username" width="98%" />
												</row>

												<!-- Password -->
												<row>
													<label
														id="label_ZKLoginDialog_pwd"
														value="${c:l('label_ZKLoginDialogPwd')}" />
													<textbox
														id="txtb_Password" type="password" name="j_password"
														width="98%" />
												</row>

												<row spans="2">
													<hbox>
														<h:input
															type="submit" value="Login" />
														<!-- 
															<button id="btnReset" 
															height="22px" label="Reset" />
														-->
													</hbox>
												</row>
											</rows>
										</grid>
									</h:form>
								</div>

							</groupbox>

						</panelchildren>
					</panel>
				</columnchildren>
			</columnlayout>

		</groupbox>

	</window>
</zk>

The interesting part of this file are the three variable names: j_tenantid, j_username, j_password . These are submitting to the server when pressing the OK button .

  • ZKLoginDialogCtrl.java

    /**
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++<br>
     * Controller for the /WEB-INF/zkloginDialog.zul file.<br>
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++<br>
     * 
     * @author bbruhns
     * @author sgerth
     */
    public class ZkLoginDialogCtrl extends GenericForwardComposer implements Serializable {
    
    	private transient final static Logger logger = Logger.getLogger(ZkLoginDialogCtrl.class);
    	private static final long serialVersionUID = -71422545405325060L;
    
    	/*
    	 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    	 * All the components that are defined here and have a corresponding
    	 * component with the same 'id' in the zul-file are getting autowired 
         * by the 'GenericForwardComposer'.
    	 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    	 */
    	protected Window loginwin; // autowired
    	protected Textbox txtb_Username; // autowired
    	protected Textbox txtb_Password; // autowired
    	protected Textbox txtb_TenantId; // autowired
    
    	/**
    	 * default constructor. <br>
    	 */
    	public ZkLoginDialogCtrl() {
    		super();
    
    		if (logger.isDebugEnabled()) {
    			logger.debug("--> super() ");
    		}
    	}
    
    	/**
    	 * Automatically called method from zk.
    	 * 
    	 * @param event
    	 * @throws Exception
    	 */
    	public void onCreate$loginwin(Event event) throws Exception {
    
    		if (logger.isDebugEnabled()) {
    			logger.debug("--> " + event.toString());
    		}
    
    		txtb_Username.focus(); // set the focus on UserName
    
    		loginwin.setShadow(false);
    		loginwin.doModal();
    	}
    
    	/**
    	 * when the "close" button is clicked. <br>
    	 * 
    	 * @throws IOException
    	 */
    	public void onClick$button_ZKLoginDialog_Close() throws IOException {
    
    		if (logger.isDebugEnabled()) {
    			logger.debug("-->");
    		}
    
    		Executions.sendRedirect("/j_spring_logout");
    	}
    
    }
    

    Ok. Back to the customizing. In this article i will not explain how spring-security works therefore’s the documentation. I will concentrate on the classes for the needed purpose. At first we have a look which classes are involved in the case of spring-security’s form-login.

    This we will done in part 2 of this article.

    Samples are hostet in the Zksample2 project on

    Have fun with it.

    Stephan Gerth

    Dipl.rer.pol.


    PS: Help to prevent the global warming by writing cool software