I was very surprised to see just how many 1 star reviews there are in the app store for this reason alone but this blog post is not about the star rating of the app.
What really surprised me is the fact that the mobile site has not been customized to accommodate the new app. Basically any user can have a full web experience with just a few taps. This can be good in certain scenarios but in this case I believe it is quite bad.
Let’s assume John buys his child an iPhone or iPod touch. He then goes and enables Parental Controls on this device.


John now gives the device to his child knowing that he has done his best to enforce the web browsing rules that he wanted. This is where the ASB iPhone app comes into play. With just a few taps (demonstrated below), John’s kid will be browsing the web freely, in a FastNet Classic branded app.



Let’s what happened here.


Obviously, from the Twitter search box John’s kid can go anywhere: whether it’s @google’s account or @LadyGaga’s it doesn’t really matter. What matters is that Twitter does not curate the links, photos or homepages that people link to. This is precisely what John wanted to prevent!
What I think happened is that the scenario I described above simply wasn’t considered.
There is a way to fix this and luckily it’s not hard at all. What I think ASB’s technical department should do is:
I have no doubt that ASB had nothing but good intentions when they decided to build this iPhone app. However they are a bank, they have lots of customers and their app is likely to be installed even by non-customers who just want to check out ASB’s offering. I am sure ASB will eventually plug this security hole and bring this to an end, however their 1 star reviews will linger and none of them (as far as I know) even touch on the issue described above.
From a technical standpoint the lesson here is that a UIWebView control can be very dangerous if careful thought is not put into how it’s used. Surely ASB does not want people to be able to tweet screenshots that have a FastNet Classic navigation bar and a collegehumour.com content view…
Cheers…
]]>
Well that shouldn't be so hard to fix though since Apache (mod_ssl) has a directive for intermediate (aka chain) certificates called SSLCertificateChainFile. I just pointed it to what I thought was the correct intermediate cert, restarted Apache, pointed Firefox to the url and tada, all good. I got this intermediate cert by simply exporting from the chain of certificates you see when double clicking on your own cert and browsing to the certificate path. What a mistake this will prove to be…
But wait, when I browsed with Safari I got a nasty "this certificate was signed by an unknown authority" error message. On my iPhone same thing, the cert failed. Tried IE7, no issues. Hmm… something was wrong. So I inspected the certificate chain and I discovered that at least in Safari my ssl cert looked as if the roor cert was VeriSign Class 3 Secure Server CA rather than Class 3 Public Primary Certification Authority (which is also Verisign,Inc and has the serial number 70 BA E4 1D 10 D9 29 34 B6 38 CA 7B 03 CC BA BF).
After googling I came across an article (don't click the link) that explains how to add an intermediate cert to Microsoft's list of trusted certificates using Microsoft Management Console and lots of fiddling. Turns out I didn't really need to do that. (told you not to click the link
)
At this point it was clear that the intermediate cert was somehow now available to all the clients. So what seemed like a logical thing to do was to see what that intermediate cert reeally looks like. Luckily there is this ssl cert checker from VeriSign. To my surprise when using it the intermediate cert was not really what I exported above. So I copied the code for the new intermediate cert and replaced the one I exported and gave it a go!
Hooray! Everything now worked.
Now what have I learned?
1. Never trust a browser to test an ssl cert. Either use openssl s_server or the applet above that verisign have built
2. Avoid Windows as a host OS for web servers. (lack of openssl, confising trusted root certificate management, etc)
3. The new cert was almost double the size of the first one. Looks like the versign intermediate cert is bundled with my cert as well. Maybe someone can clarify this?
4. Let infrastructure people handle ssl cert installation
Cheers…
]]>- copy the acegi-security-xxx.jar to your lib folder (this depends on how you have configured your project) or just include it in your build path.
- create a new xml file (i will call it ss.xml) where to put all the configuration information or copy one from the SS distribution
– import this file in your spring configuration xml (or point to it in your web.xml where you configure the contextConfigLocation)
– in the web.xml configure the Acegi Filter Chain Proxy. Then make sure the FilterToBeanProxy’s class points to the org.acegisecurity.util.FilterChainProxy (should you use FilterToBean you’ll end up writing lots and lots of xml to define the filters, but it’s your choice).
– while in the web.xml define a filter-mapping for the URIs that you want to secure. How about /* ?
All done with the web.xml You should have something like this:
[...]
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring-core.xml, /WEB-INF/classes/ss.xml</param-value>
</context-param>
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
[...]
– move to the ss.xml file that i pointed to above and configure the filter. I will have different filters for web services and for the rest of the application. The order is very important so make sure you put the more specific rules first. I will also put some stuff in /notsecured that will just not be protected.
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/webServices/**=httpSessionContextIntegrationFilterWithASCFalse,basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
/notsecured/**=#NONE#
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
Just a few comments on what we have above: the authenticationProcessingFilter could be used to audit the logon attempts as it provides onSuccessfuAuthentication and onUnsuccessfulAuthentication methods. The exceptionTranslationFilter is responsible for detecting security exceptions thrown by the AbstractSecurityInterceptor.The filterInvocationInterceptor is needed to check web URIs.
- now it’s time to configure the authenticationProcessingFilter. Here is what it should end up looking like:
<bean id=”authenticationProcessingFilter” class=”class.that.implements.AuthenticationProcessingFilter”>
<property name=”authenticationManager”>
<ref bean=”authenticationManager”/>
</property>
<property name=”authenticationFailureUrl”>
<value>/logon.jsp?tryagain=true</value>
</property>
<property name=”filterProcessesUrl”>
<value>/j_acegi_security_check</value>
</property>
<property name=”defaultTargetUrl”>
<value>/admin/home.html</value>
</property>
<property name=”alwaysUseDefaultTargetUrl”>
<value>true</value>
</property>
</bean>
So let’s look at the properties, one at a time.
First the class that implements the processing filter can be any the AuthenticationProcessingFilter itself or your own class that extends it. You would extend it to be able to do stuff when something goes wrong for example.
The authenticationManager is responsible with delegating to its list of AuthenticationPr
oviders to check an Authentication request. It can also be configured with a ConcurrentSessionController that manages how many session one user can have at a time.
The authenticationFailureUrl points to where the user will be redirected to in the event of an authentication failure. The request parameter tryagain is there to let you do some processing in the logon page.
The filterProcessesUrl is represents the URL that this filter responds to. In this example you can see the default value.
The defaultTargetUrl points to the URL that follows a successful authentication (unless the HttpSession attribute called ACEGI_SAVED_REQUEST_KEY points to somewhere else). Fully qualified URLs can be used too.
The alwaysUseDefaultTargetUrl just overrides the ACEGI_SAVED_REQUEST_KEY. Event if this key exists the redirect will still be made to the default target URL.
- it’s now time to configure the AuthenticationProvider (used by the authenticationManager above).
There are quite a few options here: DAO Authentication Provider, JAAS, Run-As, Form, Basic, Digest, Remember Me, LDAP, X509, CAS, Container Adapter, etc. I will only talk about the DAO Authentication Provider here. An authentication provider is basically a class that can process a specific Authentication implementation. The provider will use a a userDetailsService to retrieve the UserDetails object via the loadUserByUsername(String username) call.
The official documentations says: “The returned UserDetails is an interface that provides getters that guarantee non-null provision of basic authentication information such as the username, password, granted authorities and whether the user is enabled or disabled“. So basically the UserDetails object is what you all you need
You need not do anything to this DAOAuthenticationProvider unless you have specific needs. let’s have a look at how the configuration looks like:
[...]
<bean id=”daoAuthenticationProvider” class=”org.acegisecurity.providers.dao.DaoAuthenticationProvider”>
<property name=”userDetailsService”><ref bean=”jdbcDaoService”/></property>
<property name=”saltSource”><ref bean=”saltSource”/></property>
<property name=”passwordEncoder”><ref bean=”passwordEncoder”/></property>
</bean>
[...]
The userDetailsService points to a bean that is configured to retireve user details from a database. A default database structure is available but you can override the usersByUsernameQuery and the authoritiesByUsernameQuery if you need to. Typically this should be enough:
[...]
<bean id=”jdbcDaoImpl” class=”org.acegisecurity.userdetails.jdbc.JdbcDaoImpl”>
<property name=”dataSource”>
<ref bean=”dataSource”/>
</property>
</bean>
[...]
I will not explain how to configure a dataSource here.
The saltSource is just some salt that you can add to the passwords, while the passwordEncoder (e.g. Md5PasswordEncoder) is used to encode and decode the passwords in the UserDetails object returned by the UserDetailsService.
When a resource that is protected is accessed, the exceptionTranslationFilter will use the authenticationEntryPoint to load the logon form (assuming that is how we configured it). So in the ss.xml there should be something like this:
[...]
<bean id=”exceptionTranslationFilter” class=”org.acegisecurity.ui.ExceptionTranslationFilter”>
<property name=”authenticationEntryPoint”><ref local=”authenticationFilterEntryPoint”/></property>
</bean>
<bean id=”authenticationFilterEntryPoint” class=”org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint”>
<property name=”loginFormUrl”>
<value>/logon.jsp</value>
</property>
<property name=”forceHttps”>
<value>false</value>
</property>
</bean>
[...]
Now these beans look quite obvious. Let me know if you want more details on this.
The logon.jsp page itself will be built around the form. Here is a simplistic approach:
<form action=”j_acegi_security_check” method=”POST”>
<input type=”text” id=”j_username” name=”j_username”>
<br />
<input type=”password” name=”j_password”>
<br />
<input type=”submit” class=”button” value=”Logon”/>
</form>
When the form is submitted, Spring Securit
y will intercept and process it.
This is where the authorization tag libraries come into play. SS’s (lol) authorization tag lib is called authz and you can find it in the >../acegi-security-x.x.x.jar/META-INF/authz.tld
The AuthorizeTag declares three attributes: ifNotGranted, ifAllGranted, ifAnyGranted. This is also the order in which they are verified. With any of the tags you can specifiy a list of comma separated ROLEs (the whitespaces are ignored). Here’s an example taken from Ben Alex’s guide:
<authz:authorize ifAllGranted=”ROLE_SUPERVISOR”>
<td>
<A HREF=”del.htm?id=<c:out value=”${contact.id}”/>”>Del</A>
</td>
</authz:authorize>Basically, if the principal (remember the UserDetails ?) doesn’t have the ROLE_SUPERVISOR then the user won’t even see the Del anchor. So, with the ifAllGranted all roles must be granted to this principal, with ifAnyGranted at least one role must be granted while with the ifNotGranted none of the roles should be granted in order to output the code enclosed by this auth:authorize tag.
I will only mention that SS also provides support for Access Control Lists via authz:accesscontrollist. Please see the official documentation for more details.
Securing the front end is not always (okay never) enough. The better way of making sure that the business objects are not available to un-authenticated users. To protect them SS uses something called MethodSecurityInterceptor.
<bean id=”bankManagerSecurity” class=”org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor”>
<property name=”validateConfigAttributes”>
<value>true</value>
</property>
<property name=”authenticationManager”>
<ref bean=”authenticationManager”/>
</property>
<property name=”accessDecisionManager”>
<ref bean=”accessDecisionManager”/>
</property>
<property name=”runAsManager”>
<ref bean=”runAsManager”/>
</property>
<property name=”objectDefinitionSource”>
<value>
org.acegisecurity.context.BankManager.delete*=ROLE_SUPERVISOR,RUN_AS_SERVER
org.acegisecurity.context.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR,BANKSECURITY_CUSTOMER,RUN_AS_SERVER
</value>
</property>
</bean>
The important thing to look at in this example is the objectDefinitionSource. There are three ways to define with method invocations will be intercepted and checked.
1. Using a property editor
2. Using Jakarta Commons Attributes (you know, the @@SecurityConfig(“”ROLE_DUMB”) syntax…)
3. Using Java Annotations (where you will make full use of the @Secured annotation).
Here is the example from the official documentation for using java 5 style annotations:
<bean id=”attributes” class=”org.acegisecurity.annotation.SecurityAnnotationAttributes”/>
<bean id=”objectDefinitionSource” class=”org.acegisecurity.intercept.method.MethodDefinitionAttributes”>
<property name=”attributes”>
<ref local=”attributes”/>
</property>
</bean>
<bean id=”bankManagerSecurity” class=”org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor”>
<property name=”validateConfigAttributes”>
<value>false</value>
</property>
<property name=”authenticationManager”>
<ref bean=”authenticationManager”/>
</property>
<property name=”accessDecisionManager”>
<ref bean=”accessDecisionManager”/>
</property>
<property name=”runAsManager”>
<ref bean=”runAsManager”/>
</property>
<property name=”objectDefinitionSource”>
<ref bean=”objectDefinitionSource”/>
</property>
</bean>
import org.acegisecurity.annotation.Secured;
public interface BankManager {
/**
* Delete something
*/
@Secured({"ROLE_SUPERVISOR","RUN_AS_SERVER" })
public void deleteSomething(int id);
/**
* Delete another
*/
@Secured({"ROLE_SUPERVISOR","RUN_AS_SERVER" })
public void deleteAnother(int id);
}
Please note that when using BeanNameAutoProxyCreator to create the required proxy for security, the configuration must contain the property proxyTargetClass set to true. Otherwise, the method passed to MethodSecurityInterceptor.invoke is the proxy's caller, not the proxy's target.
If you want to hear the whole story, make sure you visit Acegi Security Guide. Ben Alex has done a great job documenting this framework, the API's are not too bad either, with just a few DOCUMENT ME exceptions
Cheers…