Adding a sign-in page served over HTTP but authenticating using HTTPS

On this wiki page we explain how to create a page (e.g. a public page of a site) served over HTTP but containing a sign in form that uses HTTPs for authentication.

The code we show is valid for 1.4.x branch.

We start creating a subclass of Form

import org.apache.wicket.RequestCycle;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.IModel;
import org.apache.wicket.protocol.http.RequestUtils;

/**
 * 
 * Form that does submit via HTTPs. 
 * 
 */
public class SecureForm<T> extends Form<T> 
{

	private static final long serialVersionUID = 1L;

	/**
	 * Constructor.
	 * 
	 * @param id See Component
	 */
	public SecureForm(String id) 
	{
		super(id);
	}

	/**
	 * @param id See Component
	 * @param model See Component
	 * 
	 * @see org.apache.wicket.Component#Component(String, IModel)
	 */
	public SecureForm(String id, IModel<T> model) 
	{
		super(id, model);
	}
		
	@Override
	protected void onComponentTag(ComponentTag tag) 
	{
		super.onComponentTag(tag);		
		String action = tag.getAttribute("action");
		if(!action.startsWith("http"))
			action = RequestUtils.toAbsolutePath(action);
		// rewrite action to use HTTPs
		if(!action.startsWith("https"))
			action = replacePort("https"+action.substring(4));
		tag.put("action", action);
		
	}
	
	private String replacePort(String action) {
		RequestCycle requestCycle = RequestCycle.get();
		SecureHttpsRequestCycleProcessor processor = (SecureHttpsRequestCycleProcessor)requestCycle.getProcessor();
		Integer port = processor.getConfig().getHttpPort();
		Integer httpsPort = processor.getConfig().getHttpsPort();	
		action.replace(":"+Integer.toString(port)+"/", ":"+Integer.toString(httpsPort)+"/");
		return action;
	}
}

This form rewrite the action to do submition via HTTPs.

We create an annotation to mark pages Semi secure pages... that is pages served over HTTP but containing a secure form.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SemiSecurePage {

}

We modify default HttpsRequestCycleProcessor to take care of "Semi-secure" pages, i.e. pages containing a secure form and marked with @SemiSecurePage annotation.

public class SecureHttpsRequestCycleProcessor extends WebRequestCycleProcessor
{
	private final HttpsConfig portConfig;

	/**
	 * Constructor
	 * 
	 * @param httpsConfig
	 *            configuration
	 */
	public SecureHttpsRequestCycleProcessor(HttpsConfig httpsConfig)
	{
		portConfig = httpsConfig;
	}

	/**
	 * @return configuration
	 */
	public HttpsConfig getConfig()
	{
		return portConfig;
	}

	/**
	 * Checks if the class has a {@link RequireHttps} annotation
	 * 
	 * @param klass
	 * @return true if klass has the annotation
	 */
	private boolean hasSecureAnnotation(Class<?> klass)
	{
		for (Class<?> c : klass.getInterfaces())
		{
			if (hasSecureAnnotation(c))
			{
				return true;
			}
		}
		if (klass.getAnnotation(RequireHttps.class) != null)
		{
			return true;
		}
		if (klass.getSuperclass() != null)
		{
			return hasSecureAnnotation(klass.getSuperclass());
		}
		else
		{
			return false;
		}
	}

	/**
	 * Gets page class from a request target
	 * 
	 * @param target
	 * @return page class if there is one, null otherwise
	 */
	private Class<?> getPageClass(IRequestTarget target)
	{
		if (target instanceof IPageRequestTarget)
		{
			return ((IPageRequestTarget)target).getPage().getClass();
		}
		else if (target instanceof IBookmarkablePageRequestTarget)
		{
			return ((IBookmarkablePageRequestTarget)target).getPageClass();
		}
		else
		{
			return null;
		}
	}

	/** @deprecated use checkSecureIncoming */
	@Deprecated
	protected IRequestTarget checkSecure(IRequestTarget target)
	{
		return checkSecureIncoming(target);
	}

	/**
	 * Checks if the class has a {@link RequireHttps} annotation
	 * 
	 * @param klass
	 * @return true if klass has the annotation
	 */
	private boolean hasSecureSemiSecureAnnotation(Class<?> klass)
	{
		if(klass == null)
			return false;
		
		for (Class<?> c : klass.getInterfaces())
		{
			if (hasSecureAnnotation(c))
			{
				return true;
			}
		}
		if (klass.getAnnotation(SemiSecurePage.class) != null)
		{
			return true;
		}
		if (klass.getSuperclass() != null)
		{
			return hasSecureSemiSecureAnnotation(klass.getSuperclass());
		}
		else
		{
			return false;
		}
	}
	 
	protected IRequestTarget checkSecureIncoming(IRequestTarget target)
	{

		if (target != null && target instanceof SwitchProtocolRequestTarget)
		{
			return target;
		}
		if (portConfig == null)
		{
			return target;
		}

		Class<?> pageClass = getPageClass(target);				
		
		if (pageClass != null)			
		{
			if(hasSecureSemiSecureAnnotation(pageClass)) {
				if (target instanceof IListenerInterfaceRequestTarget) {
					Component c = ((IListenerInterfaceRequestTarget) target).getTarget();
					if(SecureForm.class.isAssignableFrom(c.getClass())) {
						return target;
					}
				}
			}
			
			IRequestTarget redirect = null;
			if (hasSecureAnnotation(pageClass))
			{
				redirect = SwitchProtocolRequestTarget.requireProtocol(Protocol.HTTPS);
			}
			else
			{
				redirect = SwitchProtocolRequestTarget.requireProtocol(Protocol.HTTP);
			}
			if (redirect != null)
			{
				return redirect;
			}

		}
		return target;
	}

	protected IRequestTarget checkSecureOutgoing(IRequestTarget target)
	{

		if (target != null && target instanceof SwitchProtocolRequestTarget)
		{
			return target;
		}
		if (portConfig == null)
		{
			return target;
		}

		Class<?> pageClass = getPageClass(target);
			
		if (pageClass != null)
		{
			if(hasSecureSemiSecureAnnotation(pageClass)) {
				if (target instanceof IListenerInterfaceRequestTarget) {
					IListenerInterfaceRequestTarget interfaceRequestTarget = (IListenerInterfaceRequestTarget) target;
					Component c = interfaceRequestTarget.getTarget();
					if(SecureForm.class.isAssignableFrom(c.getClass())) {						
						RequestCycle requestCycle = RequestCycle.get();
						WebRequest webRequest = (WebRequest)requestCycle.getRequest();
						HttpServletRequest request = webRequest.getHttpServletRequest();						
						boolean isHTTPS =request.getScheme().equals(Protocol.HTTPS.name().toLowerCase());
						if(isHTTPS) {	
							if(webRequest instanceof SecureServletWebRequest) {
								((SecureServletWebRequest)webRequest).setUseAbsoluteURL(true);
							}
							return target;
						} else {
							return target;
						}
						
					}
				}
			}
			
			IRequestTarget redirect = null;
			if (hasSecureAnnotation(pageClass))
			{
				redirect = SwitchProtocolRequestTarget.requireProtocol(Protocol.HTTPS, target);
			}
			else
			{
				redirect = SwitchProtocolRequestTarget.requireProtocol(Protocol.HTTP, target);
			}
			if (redirect != null)
			{
				return redirect;
			}

		}
		return target;
	}


	/** {@inheritDoc} */
	@Override
	public IRequestTarget resolve(RequestCycle rc, RequestParameters rp)
	{
		
		if (portConfig.isPreferStateful())
		{
		// we need to persist the session before a redirect to https so the session lasts across
		// both http and https calls.
		Session.get().bind();
		}

		IRequestTarget target = super.resolve(rc, rp);
		return checkSecure(target);
	}

	/** {@inheritDoc} */
	@Override
	public void respond(RequestCycle requestCycle)
	{
		IRequestTarget requestTarget = requestCycle.getRequestTarget();
		if (requestTarget != null)
		{
			IRequestTarget secured = checkSecureOutgoing(requestTarget);
			if (secured != requestTarget)
			{
				requestCycle.setRequestTarget(secured);
				// respond will be called again because we called setrequesttarget(), so we do not
				// process it this time
				return;
			}
		}
		super.respond(requestCycle);
	}
}

Additionally we need to make a copy of class SwitchProtocolRequestTarget an place it on same package as class above (the original class is only package visible).

We create SecureBufferedWebResponse that will take care of rewriting resource URLs.

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.wicket.RequestCycle;
import org.apache.wicket.protocol.http.BufferedWebResponse;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.util.string.Strings;

import es.liberty.paneuropeo.web.https.SwitchProtocolRequestTarget.Protocol;

/**
 * @author Ernesto Reinaldo Barreiro
 *
 */
public class SecureBufferedWebResponse extends BufferedWebResponse {

	
	/**
	 * @param httpServletResponse
	 */
	public SecureBufferedWebResponse(HttpServletResponse httpServletResponse) 
	{
		super(httpServletResponse);
	}
	

	
	@Override
	public CharSequence encodeURL(CharSequence url) {
		RequestCycle requestCycle = RequestCycle.get();
		WebRequest webRequest = (WebRequest)requestCycle.getRequest();	
		if((webRequest instanceof SecureServletWebRequest) 
				&& ((SecureServletWebRequest)webRequest).isUseAbsoluteURL())
		{
			HttpServletRequest request = webRequest.getHttpServletRequest();
			boolean isHTTPS =request.getScheme().equals(Protocol.HTTPS.name().toLowerCase());
			if(isHTTPS) {
				SecureHttpsRequestCycleProcessor processor = (SecureHttpsRequestCycleProcessor)requestCycle.getProcessor();
				Integer port = null;
				if (processor.getConfig().getHttpPort() != 80)
				{
					port = processor.getConfig().getHttpPort();
				}
				String absUrl = getUrl("http", port, request, url.toString());
				return super.encodeURL(absUrl);
			}		
		}
		return super.encodeURL(url);
	}
	
	/**
	 * Rewrite the url using the specified protocol
	 * 
	 * @param protocol
	 * @param port
	 * @param request
	 * @return url
	 */
	protected String getUrl(String protocol, Integer port, HttpServletRequest request, String queryString)
	{
		if(queryString.startsWith("http") || queryString.startsWith("https"))
			return queryString;
		StringBuilder result = new StringBuilder();
		result.append(protocol);
		result.append("://");
		result.append(request.getServerName());
		if (port != null)
		{
			result.append(":");			
			result.append(port);
		}
		
		result.append(request.getRequestURI());
		if (queryString != null)
		{
			if(queryString.indexOf("../")>=0) 
			{
				queryString = Strings.replaceAll(queryString, "../", "").toString();
			} else if(!queryString.startsWith("?"))
				result.append("?");
			result.append(queryString);
		}
		return result.toString();
	}

}

and a subclass of ServletWebRequest containing a flag to identify request that need URL re-writing

import javax.servlet.http.HttpServletRequest;

import org.apache.wicket.protocol.http.servlet.ServletWebRequest;

/**
 * @author Ernesto Reinaldo Barreiro
 *
 */
public class SecureServletWebRequest extends ServletWebRequest {

	private boolean useAbsoluteURL = false;
	
	/**
	 * @param httpServletRequest
	 */
	public SecureServletWebRequest(HttpServletRequest httpServletRequest) {
		super(httpServletRequest);
	}

	public boolean isUseAbsoluteURL() {
		return useAbsoluteURL;
	}

	public void setUseAbsoluteURL(boolean useAbsoluteURL) {
		this.useAbsoluteURL = useAbsoluteURL;
	}

}

Now on your application class you declare

        protected IRequestCycleProcessor newRequestCycleProcessor() {
		return new SecureHttpsRequestCycleProcessor(new HttpsConfig());
	}
	
	@Override
	protected WebResponse newWebResponse(HttpServletResponse servletResponse) {
		return (getRequestCycleSettings().getBufferResponse() ? new SecureBufferedWebResponse(
				servletResponse) : new WebResponse(servletResponse));
	}
	
	@Override
	protected WebRequest newWebRequest(HttpServletRequest servletRequest) {
		return new SecureServletWebRequest(servletRequest);
	}

Then we are ready to use the class SecureForm


@SemiSecurePage
public  class SemiSecureLoginPage extends PublicBasePage {
                        
                         ...........

                         SecureForm =  new SecureForm("form") {

				private static final long serialVersionUID = 1L;
				
				@Override
				protected void onSubmit() {
					//do sign in logic here!		
				}	
				
				@Override
				protected void onError() {
					// do error logic here.
				}
			};
}
  • No labels