Skip to content

Servlet Container Authentication and Authorization

John Sarman edited this page Mar 13, 2014 · 24 revisions

Wicket-Servlet3-Container-Auth package is designed to simplify the integration of wicket-auth-roles with the servlet 3 security container. It is designed to support all Realms (Basic, Digest,Ldap,Spenago,etc) configured from web.xml, but the document will initially focus on the Form-Based authentication.

Overview

Typical usage is to extend the ServletContainerAuthenticatedWebApplication. By extending this class it configures two important features.

  • Enables a compile-time annotation processor to generate paths for all pages annotated with @AuthorizedInstantiation. WebPages can also be annotated with @MountPath("custom/path/whatever.html") to override the default name generation.
  • Enforces that all pages with @AuthorizedInstantiation are redirected to the mountedPath so that the security-container can handle authentication based on the specified realm.
public class ContainerManagedSecurityApp extends ServletContainerAuthenticatedWebApplication
{

	@Override
	public Class<? extends Page> getHomePage()
	{
		return HomePage.class;
	}

	@Override
	protected Class<? extends ServletContainerAuthenticatedWebSession> getContainerManagedWebSessionClass()
	{
		return ServletContainerAuthenticatedWebSession.class;
	}

	@Override
	protected Class<? extends WebPage> getSignInPageClass()
	{
		return SignInPage.class;
	}

}

Annotations

The module supports two custom annotations and the compile-time code generator uses wicket-auth-roles @AuthorizedInstantiation annotation to generate a List of IRequestMapper objects that are then mounted at Application initialization.

@SecureAutoMount

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Inherited
public @interface SecureAutoMount
{
	/**
	 * Allow the configuration of the root path (not including contextPath) for auto generated paths of classes
	 * that are not an authorized page.
	 *
	 * @return Configured root for pages not requiring security
	 */
	String defaultRoot() default "";

	/**
	 * Allow the configuration of mime type for auto generated paths of classes
	 * that are not an authorized page.
	 *
	 * @return default mime type for auto generated paths. Defaults to "html".
	 */
	String defaultMimeExtension() default "html";

	/**
	 * Allow the configuration of the root path (not including contextPath) for auto generated paths of classes
	 * that are annotated with @AuthorizedInstantiation. This includes any class that resides in a package
	 * that is also annotated with @AuthorizedInstantiation.
	 *
	 * @return Configured root for pages requiring security. Defaults to "secure".
	 */
	String secureRoot() default "secure";

	/**
	 * Allow the configuration of mime type for auto generated paths of classes
	 * that are annotated with @AuthorizedInstantiation. This includes any class that resides in a package
	 * that is also annotated with @AuthorizedInstantiation.
	 *
	 * @return default mime type for auto generated paths. Defaults to "html".
	 */
	String secureMimeExtension() default "html";

	/**
	 * Allow explicit declaration of packages that should be scanned to generate the code for
	 * auto mounts.  If not set then the scanned packages are set to the package of the application
	 * annotated with @SecureMount as the root package and all packages that are a branch of the root.
	 * If packages are explicitly set then .* to end of packages allows the branches to be scanned. If a package is
	 * set without the .* then only that package is scanned.
	 *
	 * @return String array of packages that are scanned for auto mounting.
	 */
	String[] packagesToScan() default {};

}

The ServletContainerAuthenticatedWebApplication is annotated with @SecureAutoMount using all the defaults. When an application inherits from ServletContainerAuthenticatedWebApplication, it also inherits the annotation. If the application would like to override the defaults, then Simply annotate the Application with @SecureAutoMount and set the values as required. Example

@SecureAutoMount(defaultRoot="public", defaultMimeExtension="", secureRoot="private",secureMimeExtension="shtml")

The included sample does not override the default, but above is to see the ease of changing them.

One important note is packagesToScan option.
If not set then the compile-time annotation processor will only look for annotations to process starting from the Application root package as the trunk of a tree and traverse the nodes of that tree. If you would like to specify a custom list then append a .* to the end of the package to search the package and all of the nodes. Do not use the .* if you only want to include that package. When setting the packagesToScan it no longer users the Application root as a path unless it is also explicitly set.

@MountPath

The @MountPath is included from another WicketStuff project under wicket-auto-mount. Do not confuse this @MountPath with the runtime version of automounting found in the WicketStuff project Wicket-Annotation. Those may merge in the future, but it was decided to not merge with that project so as not to conflict with the existing userbase.

package org.wicketstuff.wicket.mount.core.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author jsarman
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE})
@Documented
@Inherited
public @interface MountPath
{

	String value() default "/*";

}

Here the MountPath supports just a value option. The value is the actual path. If no value is set then the /* is replaced by the compiler with the following for two different scenarios:

Scenario 1

@MountPath is set on a class that does not have @AuthorizedInstance, this means it doesn't extend a class that has it or exist in a package the @AuthorizedInstance is set on a package-info.class.

If value is left the default "/*" then the annotation processor will replace the value with

defaultRoot + getClass().getSimpleName() + defaultMimeExtension (includes / and . if needed)

If the value is set and ends with /* then the * is replaced with getSimpleName + defaultMimeExtension

If the /* is not part of the value then that value is used.

Scenario 2

@MountPath set on package level.

If value = /* (default) then Scenario 1 applies for all Pages in the Package.

Otherwise the value is appended with /* if not present then Scenario 1 applies for all Pages in the Package. Note: the if a @MountPath exists at the class level and the package level the class level setting is used.

Scenario 3

Only an @AuthorizedInstance exists on a class. No MountPaths on the class or at package level.

In this scenario the Path is set to

secureRoot + simpleName + secureMimeExtension of course adding the / and the . when required.

Scenario 4

@AuthorizedInstance exists on package-info. This applies Scenario 3 on all Pages in the package.

Order of Precedence for creating paths.

  • @MountPath at class level
  • @MountPath at package level
  • @AuthorizedInstance class and package level

Since @MountPath has higher precedence than @AuthorizedInstance, if used together it is the developers responsibility to ensure the path set is part of the secure domains assigned in web.xml. It is always the developers responsibility to verify the secureRoot matches a security-constaint in web.xml, if not when a page annotated with @AuthorizedInstance is instantiated you will receive the unauthorized page and not the login page. This is the default behavior, even though wicket could just serve up the login page, that would then give administrators an edge when arguing about a breach.

the default is secure to adding context

Here is a sample of the auto-generated mount list Java file:

package org.wicketstuff.servlet3.secure.example;

import org.wicketstuff.wicket.mount.core.*;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.core.request.mapper.MountedMapper;
import java.util.*;

public class ContainerManagedSecurityAppMountInfo implements MountInfo
{
	@Override
	public List<IRequestMapper> getMountList() {
		List<IRequestMapper> ret = new ArrayList<IRequestMapper>();
		ret.add(new MountedMapper("index.html", org.wicketstuff.servlet3.secure.example.ui.HomePage.class));
		ret.add(new MountedMapper("secure/Page3.html", org.wicketstuff.servlet3.secure.example.ui.pkgExample.Page3.class));
		ret.add(new MountedMapper("secure/admin/admin.html", org.wicketstuff.servlet3.secure.example.ui.admin.AdminPage.class));
		ret.add(new MountedMapper("secure/Page2.html", org.wicketstuff.servlet3.secure.example.ui.user.Page2.class));
		return ret;
	}
}

The Java file is packaged in the same package of the Application. The name is the ApplicationName concatenated with MountInfo. ContainerManagedSecurityAppMountInfo from the included example application ContainerManagedSecurityApp.

How the mountPaths are set

The ServletContainerAuthenticatedWebApplication class which itself is extended version of wicket-auth-roles' AuthenticatedWebApplication, overrides the Application init file:

@Override
	protected void init()
	{
		super.init();

		// Add overrides for bookmarkable and nonbookmarkable page creations to allow servlet container
		// authorization mechanism to handle redirect to login page.
		final ContainerSecurityInterceptorListener listener = new ContainerSecurityInterceptorListener();
		getSecuritySettings().setUnauthorizedComponentInstantiationListener(listener);
		getRequestCycleListeners().add(listener);

		autoMountPages();
	}

The ContainerSecurityInterceptorListener is designed to force redirect the pages to the mounted url for pages that are bookmarkable and non-bookmarkable. This is covered later.

Notice autoMountPages(). This method first mounts all the pages in the generated MountInfo class if that class exists, then checks to see if the signInPage class exists and is not annotated with a custom mount. If a custom mount is not set using the @MountPath custom annotation, then the autoMountPages will generate a mount point and print the created mountpath as an Info listing of the logger. Mounting the signInPage is important for all form-based Realms, so that this mount point can be set in the configuration.

The Wicket Filter

Servlet 3 using annotations

@WebFilter(value = "/cms/*", dispatcherTypes = {REQUEST, FORWARD},
		initParams = {
				@WebInitParam(name = "applicationClassName",
						value = "org.wicketstuff.servlet3.secure.example.ContainerManagedSecurityApp"),
				@WebInitParam(name = "filterMappingUrlPattern", value = "/cms/*"),
				@WebInitParam(name = "configuration", value = "deployment")
		}
)
public class ContainerManagedSecurityWicketFilter extends WicketFilter
{

}

Important notes dispatherTypes is set to REQUEST and FORWARD

import static javax.servlet.DispatcherType.FORWARD;
import static javax.servlet.DispatcherType.REQUEST;

The FORWARD dispatcher will allow the SignInPage to be a Wicket page.

 <form-login-config>
            <form-login-page>/cms/login.html</form-login-page>
            <form-error-page>/cms/index.html</form-error-page>
 </form-login-config>

In the web.xml snippet the login page is set to /cms/login.html. The automounter will set the getSignInPageClass to this page if a @MountPath is not present.

If the FORWARD dispatcher is not present then when the page is needed a 404 code will be given because the filter is not configured to receive a FORWARD. This has cause confusion in the past just search google. The issue has always been that the Forward was not added as a dispatcher.

Wicket filter XML

        <filter>
            <filter-name>ContainerManagedSecurityApp</filter-name>
            <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
            <init-param>
                <param-name>applicationClassName</param-name>
                <param-value>org.wicketstuff.servlet3.secure.example.ContainerManagedSecurityApp</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>ContainerManagedSecurityApp</filter-name>
            <url-pattern>/cms/*</url-pattern>
           <dispatcher>REQUEST</dispatcher>  
           <dispatcher>FORWARD</dispatcher> 
        </filter-mapping>

Same deal here do not forget to set the dispatchers if you would like to use a SigninPage from Wicket. The example simply uses the SignInPage class found in wicket-auth-roles.

Clone this wiki locally