Documentation/Tutorials/Red5AndAcegiSecurity

Red5 and Acegi Security

The current implementation of Red5 doesn't support a way for RoleBased Security.

Acegi Security System is on the list to be implemented but it wasn't so far. So i thought, for now, how could we use Acegi with Spring and Red5?

はじめに

 Acegi Security is a powerful, flexible security solution for enterprise software, with a particular emphasis on applications that use Spring. Using Acegi Security provides applications with comprehensive authentication, authorization, instance-based access control, channel security and human user detection capabilities.

The main advantage of Ageci is the easy implementation and a standard approach of solving security matters. Rolebased with ACL permissions.

The Start

I use seperate files for the setup of acegi in spring with Red5. The mean guide for this tutorial is the acegi tutorial available in the war of acegi security bin download. The current config allows to use any red5-*.xml. So create a file named _red5-security.xml_ in your WEB-INF Folder. You can also use your own red5-security.properties config handler for variable definitions.

In this example we use the simple Memorybased Authentication, you could also use Jdbc or any other provided by acegi.

You have to create a file called users.properties

admin=secretpassword,ROLE_SUPERVISOR

You can add your users by adding new lines with this syntax.

Our red5-security.xml should look like this


<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	    <property name="location" value="/WEB-INF/red5-security.properties" />
	</bean>
	<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
		<property name="providers">
			<list>
				<ref local="daoAuthenticationProvider"/>
			</list>
		</property>
	</bean>
	<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
		<property name="userDetailsService" ref="userDetailsService"/>
		<property name="userCache">
			<bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
				<property name="cache">
					<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
						<property name="cacheManager">
							<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
						</property>
						<property name="cacheName" value="userCache"/>
					</bean>
				</property>
			</bean>
		</property>
	</bean>

	<bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
		<property name="userProperties">
			<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
				<property name="location" value="/WEB-INF/users.properties"/>
			</bean>
		</property>
	</bean>
</beans>

As you see we use a DataObjectAccess Provider compatible with a list of different Providers. We use a simple Cache based provider (EhCacheBasedUserCache) for faster authentication. (Doesn't matter, its still memory based) The service that provides the users information is the _userDetailsService_ bean. The property _userProperties_ specify the relative path to our _user.properties_ file.

Acegi security allows us to use more providers at the same time.

Implementation in our program

	@Override
	public boolean appConnect(IConnection arg0, Object[] arg1) {
		if (arg1.length==1)
		{
			if (arg1[0]!=null)
			{
				//we expect a format like this {name: "admin", password: "secret"}
				final HashMap m=(HashMap)arg1[0];
				UsernamePasswordAuthenticationToken t=new UsernamePasswordAuthenticationToken(m.get("name"),m.get("password"));
				
				ProviderManager mgr=(ProviderManager)masterScope.getContext().getBean("authenticationManager");
				try {
					//authenticate the user against our proivder
					// IMPORTANT: the returning UsernamePasswordAuthenticationToken
					//            is not the same as our UsernamePasswordAuthenticationToken
					t=(UsernamePasswordAuthenticationToken)mgr.authenticate(t);

				}
				catch(BadCredentialsException ex)
				{
					//the information provided was wrong
					rejectClient("Wrong login information");
				}
				if (t.isAuthenticated())
				{
					arg0.getClient().setAttribute("authInformation", t);
					
					// The client is authenticated
					// You can use this in your functions called by the client
					// or event the StreamPublish Security handler
					log.debug("YESS!!! AUTHENTICATED!!!!!");
				}
			}
		}
		return super.appConnect(arg0, arg1);

So this is the easy way of implementation.

Better approach

So, the above implementation is very easy and, I dont like such code in bigger apps. So, how can we provide a generic way of security, even if we have to work in an environment where we can provide anonymous authentication?

The solution is to write your own ClientRegistry or to change the Application Code

    public boolean connect(IConnection conn, IScope scope, Object[] params) {

		log.debug("Connect to core handler ?");

        // Get session id
        String id = conn.getSessionId();

		// Use client registry from scope the client connected to.
		IScope connectionScope = Red5.getConnectionLocal().getScope();

        // Get client registry for connection scope
        IClientRegistry clientRegistry = connectionScope.getContext()
				.getClientRegistry();

        // Get client from registry by id or create a new one
        IClient client = clientRegistry.hasClient(id) ? clientRegistry
				.lookupClient(id) : clientRegistry.newClient(params);

		// We have a context, and a client object.. time to init the conneciton.
		conn.initialize(client);


		// we could checked for banned clients here 
		return true;
	}

This is the main code of our Corehandler : Application. So if we override the connect, we could integrate it directly.

The problem is, that we have to copy the code because otherwise we can not inject our client ID.

So lets take a look at the client registry

	public IClient newClient(Object[] params) throws ClientNotFoundException,
			ClientRejectedException {
		IClient client = new Client(nextId(), this);
		addClient(client);
		return client;
	}

We can now extend the ClientRegistry to AuthClientRegistry.

public class AuthClientRegistry extends ClientRegistry {

	protected static Log log = LogFactory.getLog(AuthClientRegistry.class.getName());
	
	public AuthClientRegistry() {
		// TODO Auto-generated constructor stub
		super();
	}
	@Override
	public IClient newClient(Object[] params) throws ClientNotFoundException, ClientRejectedException {
   //We can do our authentication here.
}

Edit: this part is modified according Joachim Bauch

Now we have to integrate our own ClientRegistry. We have to register it on Startup as a seperate bean at our WebApp. We have to modify red5-web.xml

<bean id="authClientRegistry" class="path.to.my.AuthClientRegistry" singleton="true" />
<bean id="web.context" class="org.red5.server.Context">
    <property name="scopeResolver" ref="red5.scopeResolver" />
    <property name="clientRegistry" ref="authClientRegistry" />
    <property name="serviceInvoker" ref="global.serviceInvoker" />
    <property name="mappingStrategy" ref="global.mappingStrategy" />
</bean>

Now simply extend the Client class to AuthClient. AuthClient serves some more methods according authentication.

We will now create instead of a Client() a new AuthClient()

IClient client =new AuthClient(authenticationInformation);

Its not the smoothest solution, but because many of us are using the username as an ID we have oppertunity to simply lookup all ids with the client registry, without creating extra hash maps. But from then its very important to do a second lookup if the id does not already exist before we create our new client.

In the application we could use a simple casting.

if (client is AuthClient)
{
    ((AuthClient)client).getAuthInfo().isAuthenticated(); //as a sample usage
}

Feature: We have not to lookup in the provided HashMap for the Authentication and we could even extend it for bigger programms.

Ok, i dont know if this is a really _legal_ method of injecting clients because i dont know if its changing some logic of red5. Maybe someone of the core developers give a comment on this approach. But i can promise, it works and i use it in production. ;-)

Hope it help.

cu nomIad