10.3 Security
This section should be read in conjunction with the section on general security in CloudTran Security Concepts.
Security in SaaS is made slightly more complicated than standard security because in SaaS applications need to know the TenantId as well as the authorised
user. Other parts of the application may need to use the TenantId just as they may need to obtain the user credentials.
Holding the TenantId in the Security Context can be achieved in a number of ways, but given that the default security model in CloudTran uses a Spring's database backed
authentication it seemed sensible to use the same approach. Even then there are still a number of ways to achieve this such as creating a new UserDetails object that contain
the TenantId, hooking that into the Spring model. However, the simplest approach was to keep the UserDetails structure and instead change the user details stored in
the database. So in the SaaS applications the the user's login name is amended by concatinating the user's name and the TenantId are concatinated. For example:
Username : Bob
TenantId : Tenant1
New username : Bob:Tenant1
The user still logs in with their username 'Bob', but the username held in the security context is 'Bob:Tenant1'. To achieve this the Spring UsernamePasswordAuthenticationFilter class
has been overridden with a CloudTran class 'CTUsernamePasswordAuthenticationFilter'. This class is the core CloudTran jar, but the key amendment in this class where
the username is concatinated with the TenantId is shown below.
@Override
public Authentication attemptAuthentication( HttpServletRequest request,
HttpServletResponse response )
throws AuthenticationException
{
if ( postOnly && !request.getMethod().equals( "POST" ) )
{
throw new AuthenticationServiceException( "Authentication method not supported: " +
request.getMethod() );
}
String username = obtainUsername( request );
String password = obtainPassword( request );
String tenatId = obtainTenantId( request );
if ( username == null )
{
username = "";
}
if ( tenatId == null )
{
tenatId = "";
}
if ( password == null )
{
password = "";
}
username = username.trim() + ":" + tenatId.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken
( username, password );
// Allow subclasses to set the "details" property
setDetails( request, authRequest );
return this.getAuthenticationManager().authenticate( authRequest );
}
This of course means that we need to inject the 'CTUsernamePasswordAuthenticationFilter'.
The MultiTenancy example - examples/CloudTran/MultiTenancy - shows how to do this, in the SpringSecurityExample file
workspace/SecuritySpringExample/WebContent/WEB-INF/applicationContext-security.xml
...
<beans:bean id="customUsernamePasswordAuthenticationFilter"
class="com.cloudtran.web.CTUsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationFailureHandler" ref="failureHandler" />
<beans:property name="authenticationSuccessHandler" ref="successHandler" />
</beans:bean>
...
What this means is that the username in the security context is not the user's login name, so the other piece of the jigsaw is to provide a security utility class
that retrieves the user's login name and the TenantId from the security context.
This SecurityUtils class is generated in the ..._CtFramework project in the SecurityUtils class.
For example, in the MultiTenancy application - at examples/CloudTran/MultiTenancy - this file is at
workspace/SaaSHR_CtFramework/src/com/cloudtran/servlet/SecurityUtils.java:
...
/**
* This gets the tenantId in the principal.
* If it is qualified with ':', return just the tenantId
*/
public static String getTenantId()
{
int ix = getPrincipalName().lastIndexOf( ":" );
if( ix < 0 )
return getPrincipalName();
return getPrincipalName().substring( ix + ":".length() );
}
/**
* This gets the user name from the principal in the current security context
*/
public static String getName()
{
int ix = getPrincipalName().indexOf( ":" );
if( ix < 0 )
return getPrincipalName();
return getPrincipalName().substring( 0, ix );
}
/**
* This gets the principal name from the current security context
*/
public static String getPrincipalName()
{
return SecurityContextHolder.getContext().getAuthentication().getName();
}
...
This solution is not designed to be a comprehensive solution but merely enough to show how security can be added to a SaaS application simply and quickly.
Amend it to suit your requirements.
|