Wednesday, February 22, 2012

Convincing IE to not remove window.location.hash on a redirect

Our system has to work well with the most used browsers. This is usually not a big deal as longas you don't check your page on IE. Unfortunately it is still among the most used ones, so any serious web page has to consider it.

Lately we faced the problem, that whenever spring security has decided that the login is not valid anymore, and redirected the user to the login page the query string and the hash are removed (only in IE) and the user looses the reference to the page he/she was before.

There are two solutions to this problem. The first is just a parameter in the authentication entry point of your website

<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint" >
<constructor-arg name="loginFormUrl" value="/login.jsf" / >
<property name="useForward" value="true"/ >
</bean>

Forwarding instead of redirecting leaves the URL unchanged when the page displays the login page, so IE has no change to remove the parameters. Unfortunately this solutions shows the login page probably on every page of your website. If the user uses a password inserting tool based on URLs this will not work anymore, and of course it is not transparent for the user to enter its credentials once on the login page and than on another page.

The second solution I found on the Internet (so its not really my Idea). Basically it consists of adding a new filter in the filter chain on first position
<custom-filter ref="retainAnchorFilter" position="FIRST" />
This filter is than defined like this

<bean id="retainAnchorFilter" class="it.unibz.ict.utils.RetainAnchorFilter">
 <constructor-arg name="storeUrlPattern" value="${local.url}/login.*" />
<constructor-arg name="restoreUrlPattern" value=".*/${local.appname}/.*" />
<constructor-arg name="cookieName" value="TARGETANCHOR" />
</bean>
This filter will than store the hash when the URL matches the storeUrlPattern and restore it on the restoreUrlPattern. Actually we don't use the restore feature, because on the login page we read the parameters into a hidden field and send it using the normal form submission which fits better into our architecture, but for this sample I preferred to have it complete.

public class RetainAnchorFilter extends GenericFilterBean {
private final String storeUrlPattern;
private final String restoreUrlPattern;
private final String cookieName;
public RetainAnchorFilter(String storeUrlPattern, String restoreUrlPattern, String cookieName) {
this.storeUrlPattern = storeUrlPattern;
this.restoreUrlPattern = restoreUrlPattern;
this.cookieName = cookieName;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
if (response instanceof HttpServletResponse) {
response = new RedirectResponseWrapper((HttpServletResponse) response);
}
chain.doFilter(request, response);
}
/**
* HttpServletResponseWrapper that replaces the redirect by appropriate Javascript code.
*/
private class RedirectResponseWrapper extends HttpServletResponseWrapper {
public RedirectResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public void sendRedirect(String location) throws IOException {
HttpServletResponse response = (HttpServletResponse) getResponse();
String redirectPageHtml = "";
if (location.matches(storeUrlPattern)) {
redirectPageHtml = generateStoreAnchorRedirectPageHtml(location);
} else if (location.matches(restoreUrlPattern)) {
redirectPageHtml = generateRestoreAnchorRedirectPageHtml(location);
} else {
super.sendRedirect(location);
return;
}
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(redirectPageHtml.length());
response.getWriter().write(redirectPageHtml);
}
private String generateStoreAnchorRedirectPageHtml(String location) {
StringBuilder sb = new StringBuilder();
sb.append("<html><head><title>Redirect Page</title>\n");
sb.append("<script type=\"text/javascript\">\n");
// store anchor
sb.append("document.cookie = '" + cookieName + "=' + window.location.hash + '; path=/';\n");
// redirect
sb.append("window.location = '" + location + "' + window.location.hash;\n");
sb.append("</script>\n</head>\n");
sb.append("<body><h1>Redirect Page (Store Anchor)</h1>\n");
sb.append("Should redirect to " + location + "\n");
sb.append("</body></html>\n");
return sb.toString();
}
@SuppressWarnings("unused")
private String generateRestoreAnchorRedirectPageHtml(String location) {
StringBuilder sb = new StringBuilder();
sb.append("<html><head><title>Redirect Page</title>\n");
sb.append("<script type=\"text/javascript\">\n");
// generic Javascript function to get cookie value
sb.append("function getCookie(name) {\n");
sb.append("var cookies = document.cookie;\n");
sb.append("if (cookies.indexOf(name + '=') != -1) {\n");
sb.append("var startpos = cookies.indexOf(name)+name.length+1;\n");
sb.append("var endpos = cookies.indexOf(\";\",startpos)-1;\n");
sb.append("if (endpos == -2) endpos = cookies.length;\n");
sb.append("return unescape(cookies.substring(startpos,endpos));\n");
sb.append("} else {\n");
sb.append("return false;\n");
sb.append("}}\n");
// get anchor from cookie
sb.append("var targetAnchor = getCookie('" + cookieName + "');\n");
// append to URL and redirect
sb.append("if (targetAnchor) {\n");
sb.append("window.location = '" + location + "' + targetAnchor;\n");
sb.append("} else {\n");
sb.append("window.location = '" + location + "';\n");
sb.append("}\n");
sb.append("</script></head>\n");
sb.append("<body><h1>Redirect Page (Restore Anchor)</h1>\n");
sb.append("Should redirect to " + location + "\n");
sb.append("</body></html>\n");
return sb.toString();
}
}
}

The latter is for sure the better solution regarding transparency and in our case necessary because we have to redirect to CAS which would not be possible with the former.


Friday, February 3, 2012

CentOS: Bind multiple IP-Adresses to a Network Interface

First thing is to define a single IP address for your host.

1) CentOS(RedHat) uses the file /etc/sysconfig/network to read the saved hostname at system boot. This is set using the init script /etc/rc.d/rc.sysinit
GATEWAY=192.168.0.254
HOSTNAME=www.mydomain.it
NETWORKING=yes
FORWARD_IPV6=yes
2) We define the nameserver for this system /etc/resolv.conf
search mydomain.it
nameserver 10.10.14.1
nameserver 10.10.14.2
3) Locally resolve node names to IP addresses /etc/hosts
127.0.0.1       localhost.localdomain   localhost       name        name.mydomain.it
::1             localhost6.localdomain6 localhost6      name        name.mydomain.it

16.18.24.241     name.mydomain.it          name
16.18.24.244     name.mydomain.it          name
4) Define the first ethernet connector /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
ONBOOT=yes
USERCTL=no
BOOTPROTO=none
NETMASK=255.255.255.128
IPADDR=192.168.0.241
PEERDNS=no

check_link_down() {
    return 1;
}
TYPE=Ethernet
IPV6INIT=no
5) Copy this configuration file to a virtual one, which could be for example eth0:0
cp ifcfg-eth0 ifcfg-eth0:0
6) And than change the settings in the newly created file /etc/sysconfig/network-scripts/ifcfg-eth0:0
DEVICE=eth0:0
ONBOOT=yes
USERCTL=no
BOOTPROTO=none
NETMASK=255.255.255.128
IPADDR=192.168.0.242
PEERDNS=no

check_link_down() {
    return 1;
}
TYPE=Ethernet
IPV6INIT=no
7) Restart the network to put it to work
service network restart

Golang setup PATH

Quite recently we startet in the company to use and write some Go programs. I love Go. It's easy to learn, read and modify. One of the m...