Wicket 6.x, using HttpsMapper

Inside of the Application.init() method add the following.

It is extemtely important that when setting the rootRequestMapper it is done AFTER you have added any bookmarkable links otherwise they will not be switched over to Secure mode.


mountPage("/somepage", MyPage.class);

// notice that in most cases this should be done as the
// last mounting-related operation because it replaces the root mapper
setRootRequestMapper(new HttpsMapper(getRootRequestMapper(), new HttpsConfig()));

As with previous versions of Wicket, now all you need to do is include the @RequireHttps attribute on your Pages or Components.

Using other methods to determine if Https should be use:

Referencing the example above, override the getDesiredSchemeFor method.

setRootRequestMapper(new HttpsMapper(getRootRequestMapper(), new HttpsConfig()){
      @Override
      protected Scheme getDesiredSchemeFor(Class pageClass) {
         if (getConfigurationType()==RuntimeConfigurationType.DEVELOPMENT) {
         log.debug("Dev mode, always use HTTP");
         return Scheme.HTTP;
      } else {
         log.debug("not in development mode, letting the mapper decide, or roll you own solution");
         return super.getDesiredSchemeFor(pageClass);
      }
   }
});

Using HttpsRequestCycleProcessor (after 1.4--rc3)

By replacing the default WebRequestCycleProcessor with the HttpsRequestCycleProcessor, you are able to specify secure pages using the @RequireHttps annotation on your pages. If you wanted a little more control... lets say for development you did not want your annotated pages to use https, you could bypass the Switch Protocol code like this. As of 1.4-rc3 Wicket provides built in support for http/https switching via org.apache.wicket.protocol.https.HttpsRequestCycleProcessor. Please see the javadoc of this class for details

@Override
protected IRequestCycleProcessor newRequestCycleProcessor()
{
    HttpsConfig config = new HttpsConfig(80,443);
    return new HttpsRequestCycleProcessor(config)
    {

        @Override
        protected IRequestTarget checkSecureIncoming(IRequestTarget target)
        {
            if (getConfigurationType().equals(Application.DEVELOPMENT))
            {
	        return target;
            }
            else
            {
                return super.checkSecureIncoming(target);
            }
        }

        @Override
        protected IRequestTarget checkSecureOutgoing(IRequestTarget target)
        {
            if (getConfigurationType().equals(Application.DEVELOPMENT))
            {
                return target;
            }
            else
            {
                return super.checkSecureOutgoing(target);
            }
        }

    };
}

For The Entire Application

web.xml
<web-app ...>
   <security-constraint>
      <user-data-constraint>
         <transport-guarantee>CONFIDENTIAL</transport-guarantee>
      </user-data-constraint>
   </security-constraint>
</web-app>

Quote from web-app_2_3.dtd:

The transport-guarantee element specifies that the communication
between client and server should be NONE, INTEGRAL, or
CONFIDENTIAL. NONE means that the application does not require any
transport guarantees. A value of INTEGRAL means that the application
requires that the data sent between the client and server be sent in
such a way that it can't be changed in transit. CONFIDENTIAL means
that the application requires that the data be transmitted in a
fashion that prevents other entities from observing the contents of
the transmission. In most cases, the presence of the INTEGRAL or
CONFIDENTIAL flag will indicate that the use of SSL is required.

For Particular Pages

An explanation of how to transparently switch from http to https.

Table of contents

Change Render Strategy

Change the rendering strategy in your application.

getRequestCycleSettings().setRenderStrategy(Settings.ONE_PASS_RENDER);

I could not get this to work with the other strategies because you cannot send more than one redirect in the same request. You lose some features like avoiding double submit, but you can avoid that with some javascript. You gain some performance because the rendering is also done in the same request.

Create RequiredSSL Annotation

Create an annotation.

@Retention(RetentionPolicy.RUNTIME)
@Inherited //For a "BasePage" strategy
 public @interface RequiredSSL { }

Create New Response Strategy

Check if the response page's class has the @RequiredSSL annotation. If it does, redirect to the response page in SSL mode.

The following goes in your extended Application class.

@Override
 protected IRequestCycleProcessor newRequestCycleProcessor() {
 	                return new DefaultWebRequestCycleProcessor() {
 	                        @Override
 	                        protected IResponseStrategy newResponseStrategy() {
 	                                return new IResponseStrategy() {
 	                                        public void respond(RequestCycle requestCycle) {
 	                                                IRequestTarget requestTarget = requestCycle
 	                                                                .getRequestTarget();
 	                                                if (requestTarget != null) {
 	                                                        Application.get().logResponseTarget(requestTarget);

 	                                                        WebRequest webRequest = (WebRequest) requestCycle
 	                                                                        .getRequest();
 	                                                        WebResponse webResponse = (WebResponse) requestCycle
 	                                                                        .getResponse();

 	                                                        HttpServletRequest httpServletRequest = webRequest
 	                                                                        .getHttpServletRequest();

 	                                                        Class pageClass = null;

 	                                                        if (requestTarget instanceof IPageRequestTarget) {
 	                                                                IPageRequestTarget pageTarget =
                                                                                     (IPageRequestTarget) requestTarget;
 	                                                                pageClass = pageTarget.getPage().getClass();
 	                                                        } else if (requestTarget instanceof IBookmarkablePageRequestTarget) {
 	                                                                IBookmarkablePageRequestTarget bookmarkableTarget =
                                                                                     (IBookmarkablePageRequestTarget) requestTarget;
 	                                                                pageClass = bookmarkableTarget.getPageClass();
 	                                                        }
                                                                if (pageClass != null
 	                                                                        && !httpServletRequest.isSecure()
 	                                                                        && pageClass.isAnnotationPresent(RequiredSSL.class)) {
 	                                                                StringBuffer url = new StringBuffer("https://"
 	                                                                                + httpServletRequest.getServerName());

 	                                                                url.append(":" + MyApplication.get().getSslPort());
                                                                        String q = RequestCycle.get().urlFor(
 	                                                                                requestTarget).toString();
 	                                                                url.append(q);
 	                                                                webResponse.redirect(url.toString());
 	                                                        }
                                                                else /* else added */
 	                                                                requestTarget.respond(requestCycle);
 	                                                }
                                                 }
 	                                };
 	                        }
 	                };
 	        }

There are 2 important pieces of configuration that will change based on your environment.

1. SSL Port: There is no way to determine the SSL port being used via the servlet spec API, so this needs to be set manually. Grab the SSL port from your configuration. (Or better yet, set it with Spring in your applicationContext.xml from a properties file)

2. Hostname: In the code below, httpServletRequest.getServerName() is used to determine the hostname. This may not always work, for example in a clustered environment where your website's hostname resolves to an IP address on a router and each application server has a unique hostname like appserver1 and appserver2. If you have this kind of setup, it would be best to grab the hostname from a configuration file or set it with Spring.

Annotate Your Pages

Add @RequiredSSL to any Page that requires SSL or to your BasePage, in case of use @Inherited annotation in RequiredSSL class.

Edit:

I tried to apply this but I think there was a bug (at least it didn't work for me). A else was missing before the requestTarget.respond(requestCycle); (see else added in the code).
Additionally, a switch back to non-ssl mode should/could be added by adding a if clause, more ore less like this:

else if (pageClass != null
 	                                                                        && httpServletRequest.isSecure()
 	                                                                        && !pageClass.isAnnotationPresent(RequiredSSL.class)) {
 	                                                                StringBuffer url = new StringBuffer("http://"
 	                                                                                + httpServletRequest.getServerName());

                                                                        String q = RequestCycle.get().urlFor(
 	                                                                                requestTarget).toString();
 	                                                                url.append(q);
 	                                                                webResponse.redirect(url.toString());
 	                                                        }

Edit (Wicket 1.3.x):

I was not able to get the above to work with 1.3. However, with a few tweaks, I was able to get the proper URL generation. I followed the RequiredSSL annotation requirement. Removed the One Pass Through Render Strategy suggestion. I then copied the WebRequestCycleProcessor locally. Modified the

encode(final RequestCycle requestCycle, final IRequestTarget requestTarget)

and added a

doPostEncode()
public final CharSequence encode(final RequestCycle requestCycle,
			final IRequestTarget requestTarget)
	{
		// First check to see whether the target is mounted
		CharSequence url = pathForTarget(requestTarget);

		if (url != null)
		{
			// Do nothing - we've found the URL and it's mounted.
		}
		else if (requestTarget instanceof IBookmarkablePageRequestTarget)
		{
			url = encode(requestCycle, (IBookmarkablePageRequestTarget)requestTarget);
		}
		else if (requestTarget instanceof ISharedResourceRequestTarget)
		{
			url = encode(requestCycle, (ISharedResourceRequestTarget)requestTarget);
		}
		else if (requestTarget instanceof IListenerInterfaceRequestTarget)
		{
			url = encode(requestCycle, (IListenerInterfaceRequestTarget)requestTarget);
		}
		else if (requestTarget instanceof IPageRequestTarget)
		{
			// This calls page.urlFor(IRedirectListener.INTERFACE), which calls
			// the function we're in again. We therefore need to jump out here
			// and return the url immediately, otherwise we end up prefixing it
			// with relative path or absolute prefixes twice.
			return encode(requestCycle, (IPageRequestTarget)requestTarget);
		}
		// fallthough for non-default request targets
		else
		{
			url = doEncode(requestCycle, requestTarget);
		}

		if (url != null)
		{
			// Add the actual URL. This will be relative to the Wicket
			// Servlet/Filter, with no leading '/'.
			PrependingStringBuffer prepender = new PrependingStringBuffer(url.toString());

			// Prepend prefix to the URL to make it relative to the current
			// request.
			prepender.prepend(requestCycle.getRequest().getRelativePathPrefixToWicketHandler());

// CHANGES vvvvv
			// We need to special-case links to the home page if we're at the
			// same level.
			if (prepender.toString().length() == 0)
			{
				prepender.prepend("./");
			}

			String result = prepender.toString();

			return doPostEncode( requestCycle, requestTarget, requestCycle.getOriginalResponse().encodeURL(result) );

// CHANGES ^^^^^

		}

		// Just return null intead of throwing an exception. So that it can be
		// handled better
		return null;
	}

// CHANGES vvvvv
	protected CharSequence doPostEncode( final RequestCycle requestCycle,
			final IRequestTarget requestTarget, CharSequence result ) {

		return result;
	}

// CHANGES ^^^^^

Then used the following SecureWebRequestCycleProcessor

public class SecureWebRequestCodingStrategy extends WebRequestCodingStrategy {
	private int httpPort = 80;
	private int httpsPort = 443;

	public SecureWebRequestCodingStrategy(int httpPort, int httpsPort ) {
		this.httpPort = httpPort;
		this.httpsPort = httpsPort ;
	}

	@Override
	protected CharSequence doPostEncode(final RequestCycle requestCycle,
			final IRequestTarget requestTarget, final CharSequence result) {
		PrependingStringBuffer buf = new PrependingStringBuffer(
				stripWicketPath(result.toString()));
		if (requestTarget != null) {
			WebRequest webRequest = (WebRequest) requestCycle.getRequest();

			HttpServletRequest httpServletRequest = webRequest
					.getHttpServletRequest();

			Class targetClass = null;

			if (requestTarget instanceof IBookmarkablePageRequestTarget) {
				targetClass = ((IBookmarkablePageRequestTarget) requestTarget)
						.getPageClass();
			} else if (requestTarget instanceof ISharedResourceRequestTarget) {
				targetClass = ((ISharedResourceRequestTarget) requestTarget)
						.getClass();
			} else if (requestTarget instanceof IListenerInterfaceRequestTarget) {
				targetClass = ((IListenerInterfaceRequestTarget) requestTarget)
						.getTarget().getClass();
			} else if (requestTarget instanceof IPageRequestTarget) {
				targetClass = ((IPageRequestTarget) requestTarget).getPage()
						.getClass();
			}

			if (targetClass != null && !httpServletRequest.isSecure()
					&& targetClass.isAnnotationPresent(RequiredSSL.class)) {
				StringBuffer url = new StringBuffer("https://"
						+ httpServletRequest.getServerName());

				if (443 != httpsPort ) {
					url.append(":" + httpsPort );
				}
				url.append(httpServletRequest.getContextPath());

				url
						.append(stripServletPath(httpServletRequest
								.getServletPath()));

				buf.prepend(url.toString());
			} else if (targetClass != null && httpServletRequest.isSecure()
					&& !targetClass.isAnnotationPresent(RequiredSSL.class)) {
				StringBuffer url = new StringBuffer("http://");
				url.append(httpServletRequest.getServerName());
				if (80 != httpPort ) {
					url.append(":" + httpPort );
				}
				url.append(httpServletRequest.getContextPath());
				url
						.append(stripServletPath(httpServletRequest
								.getServletPath()));

				buf.prepend(url.toString());
			} else {
				if (!buf.toString().startsWith("/")) {
					buf.prepend("/");
				}
				buf.prepend(stripServletPath(httpServletRequest
						.getServletPath()));
				buf.prepend(httpServletRequest.getContextPath());
			}
		}

		return buf.toString();
	}

	private String stripServletPath(String fullPath) {
		int idx = fullPath.indexOf("/", 1);

		return (idx == -1) ? fullPath : fullPath.substring(0, idx);
	}

	private String stripWicketPath(String fullPath) {
		String tmp = fullPath;

		int idx = -1;

		if ((idx = tmp.indexOf("?wicket")) != -1) {
			tmp = "/" + tmp.substring(idx);
		} else if ((idx = tmp.lastIndexOf("../")) != -1) {
			tmp = "/" + tmp.substring(idx + 3);
		}
		else if ( !tmp.startsWith( "/") ) {
			tmp = "/" + tmp;
		}

		return tmp;
	}
}

In my application class, I did the following:

protected IRequestCycleProcessor newRequestCycleProcessor() {
		return new WebRequestCycleProcessor() {
			@Override
			protected IRequestCodingStrategy newRequestCodingStrategy() {

				String httpPort = MyApplication.this.getPublicPort();
				int httpPortVal = 80;

				if ( httpPort != null && !httpPort.equals( "" )) {
					httpPortVal = new Integer( httpPort ).intValue();
				}
				String httpsPort = MyApplication.this.getSslPort();

				if ( httpsPort == null || httpsPort.equals( "" )) {
					return super.newRequestCodingStrategy();
				}
				return new SecureWebRequestCodingStrategy( httpsPortVal, new Integer( httpsPort).intValue() );
			}
		};
	}

By using the above solution, anytime I placed a @RequiredSSL on a Form, Link, Button declaration, the proper URL will be generated (https for secured items and http for non-secure items).

BTW...to get Button/SubmitLink to generate https URLs you will need to use the following classes:

@RequiredSSL
public class SecureButton extends Button {

	public SecureButton(String id, IModel model) {
		super(id, model);
	}

	public SecureButton(String id) {
		super(id);
	}

}

@RequiredSSL
public class SecureSubmitLink extends SubmitLink {

	public SecureSubmitLink(String id, Form form) {
		super(id, form);
	}

	public SecureSubmitLink(String id, IModel model, Form form) {
		super(id, model, form);
	}

	public SecureSubmitLink(String id, IModel model) {
		super(id, model);
	}

	public SecureSubmitLink(String id) {
		super(id);
	}

}

Edit (Wicket 1.3.x) alternative:

An alternative implementation for 1.3 is to do as in the original solution but just override the newRequestCycleProcessor(...) in your application like this:

@Override
protected IRequestCycleProcessor newRequestCycleProcessor() {
    return new WebRequestCycleProcessor() {
        public void respond(RequestCycle requestCycle) {
            IRequestTarget requestTarget = requestCycle.getRequestTarget();
            if (requestTarget != null) {
                WebRequest webRequest = (WebRequest) requestCycle .getRequest();
                WebResponse webResponse = (WebResponse) requestCycle .getResponse();
                HttpServletRequest httpServletRequest = webRequest.getHttpServletRequest();

                Class pageClass = null;
                if (requestTarget instanceof IPageRequestTarget) {
                    IPageRequestTarget pageTarget = (IPageRequestTarget) requestTarget;
                    pageClass = pageTarget.getPage().getClass();
                } else if (requestTarget instanceof IBookmarkablePageRequestTarget) {
                    IBookmarkablePageRequestTarget bookmarkableTarget = (IBookmarkablePageRequestTarget) requestTarget;
                    pageClass = bookmarkableTarget.getPageClass();
                }

                if (pageClass != null && !httpServletRequest.isSecure() &&
                        pageClass.isAnnotationPresent(RequiredSSL.class)) {
                    // We should switch to https
                    StringBuffer url = new StringBuffer("https://");
                    url.append(httpServletRequest.getServerName());
                    url.append(":" + MyApplication.get().getHttpsPort());

                    url.append(webRequest.getHttpServletRequest().getContextPath());
                    url.append(webRequest.getServletPath());
                    webResponse.redirect(url.toString());
                } else if (pageClass != null && httpServletRequest.isSecure() &&
                        !pageClass.isAnnotationPresent(RequiredSSL.class)) {
                    // We should switch to http
                    StringBuffer url = new StringBuffer("http://");
                    url.append(httpServletRequest.getServerName());
                    url.append(":" + MyApplication.get().getHttpPort());

                    url.append(webRequest.getHttpServletRequest().getContextPath());
                    url.append(webRequest.getServletPath());
                    webResponse.redirect(url.toString());
                } else {
                    // We should just respond!
                    requestTarget.respond(requestCycle);
                }
            }
        }
    };
}
  • No labels

1 Comment

  1. Using web.xml security (as displayed on top), and different mount points like

    mountBookmarkablePage("/cad", Inicial.class);
    mountBookmarkablePage("/res", Resultado.class);
    mountBookmarkablePage("/admin/results", AdminResults.class);
    mountBookmarkablePage("/admin/users", AdminUsers.class);
    

    I think its easier than doing all the code displayed before. And dismiss the application developer to know about server infrastructure (port and hostname)

    <security-constraint>
        <display-name>ssl-test</display-name>
        <web-resource-collection>
            <web-resource-name>resultado</web-resource-name>
            <description/>
            <url-pattern>/admin/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>HEAD</http-method>
            <http-method>PUT</http-method>
            <http-method>OPTIONS</http-method>
            <http-method>TRACE</http-method>
            <http-method>DELETE</http-method>
        </web-resource-collection>
        <user-data-constraint>
            <description>ssl mode</description>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>