March 11th, 2013

Authenticating to Alfresco using Apache Auth (eg for SSO)

I'm currently setting up SSO (Single Sign On) for an Alfresco + Share install. I know I'm going to need to do a bit of customising, but I've struggled a bit with understanding what's going on with the various SSO tutorials. They give instructions, but not always a lot of explanation, which can be tricky if you know you want to go off piste. So, having reminded myself how the WebScript /services/ + /wcservices/ authentication works, my next step is to get some basic Apache Authentication in place, and have Alfresco trust that.

Why basic Apache Authentication? Well, it's the basis on which things like Alfresco + CAS and Alfresco + Shibboleth work, it can be used for SSL client cert auth, it's fairly easy to setup, I know most of the pieces already (always a bonus!). My aim in this - get Alfresco Explorer and the /wcservices/ WebScripts trusting Apache provided authentication. I'm not looking at Share, that's a follow-on post.

So, what components do we need? Firstly, there's the Alfresco External Authentication Subsystem. Next there's Tomcat AJP-13. There's Apache mod_jk to talk AJP-13. Finally, there's the basic Apache Authentication. Because I just want to test, I'm using a simple file based Apache auth setup. You likely won't use that in production, but it'll get you most of the way to a working SSO setup, and is easy to test with.

Our first thing to do is tell Alfresco that it can trust the remote authentication from Apache. (The process for this is similar to enabling Header based external Authentication). To do this, we need to enable + configure another Authentication SubSystem, specifically the External Auth SubSystem. Create a directory alfresco/extension/subsystems/Authentication/external/external-apache in your usual customisation spot (typically a module or the Tomcat Shared Classes). Into this new directory, create, and populate it with something like:
# Which users should be treated as an Admin?
# Enable the External Authentication
That's not all though, you also need to add this new authentication subsystem to the active list. Somewhere, quite possibly in your file you should have an authentication.chain line. Prepend this with external-apache:external, something like:
# This is what we used to have, LDAP + Built-in

# Try the External Auth if we can, otherwise the previous ones
Restart Alfresco, and check that you can log in as before. You should also see (assuming the default logging settings) something like INFO [] Starting 'Authentication' subsystem, ID: [Authentication, managed, external-apache] in your logs to show it was found and used.

Next up, we need to enable AJP-13 in Tomcat, and tell it to trust Apache supplied Authentication. I'm assuming you have two Tomcats, one for Alfresco on port 8080, and one for Share on 8081. If you only have one, skip the Share/8081/8010 bits. Edit [Tomcat Root]/conf/server.xml; for both Tomcats. Around line 90, you should come across an AJP Connector section, which may be commented out. Uncomment it, set the Alfresco one to port 8009 (default), and the share one to 8010. Finally, add the attribute tomcatAuthentication="false" to the Connector. This should give you something on the Alfresco Tomcat looking like:
   <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" tomcatAuthentication="false" />
And for Share it'll be:
   <!-- Define an AJP 1.3 Connector on port 8010 (Alternate Port) -->
    <Connector port="8010" protocol="AJP/1.3" redirectPort="8443" tomcatAuthentication="false" />
Restart your tomcats, and ensure they come up without error. Check you can telnet to localhost 8009 and 8010 (there's no welcome banner) to ensure the Tomcats are listening and no local firewalling breaks.

Half way!

If you haven't already, install a copy of Apache (latest 2.4, or perhaps even 2.5/2.6 if that's out by the time you read this!), and make sure you can get the welcome page up. If on a Unix, make sure you have the Apache developer tools package installed too, if you're doing a package install. If you can, install a packaged version of the Tomcat Connectors module. Failing that, grab the source code and build + install the module

There are two parts to configuring up mod_jk, the first is a JK specific file, the second is the regular Apache config. For the former, create a new file somewhere sensible (but make sure it's not somewhere that Apache auto-loads config files from, or you'll get errors). FWIW, I created a new directory under /etc/apache2/ of conf.other and created it as but it'll depend on your distro. Assuming you setup your Tomcat AJP as above, create the file with:
# For communicating with Tomcat via AJP13

# We have two Tomcats, and we'd like status please

# Enable the status worker

# Alfresco is on 8080 (http) / 8009 (AJP)

# Share is on 8081 (http) / 8010 (AJP)

Now, define a new vhost to Apache, either by tacking it on the end of your httpd.conf, or putting it in a new file in conf.d, or in sites-enabled, or .... as appropriate for your setup. To start with, go for something like:
# Load the mod_jk module (your path to it may be different)
LoadModule jk_module /usr/lib/apache2/modules/

# These paths may need changing for your setup too
# Where the file we created above lives
JkWorkersFile /etc/apache2/conf.other/
# Where to put jk shared memory
JkShmFile     /var/log/apache2/mod_jk.shm
# Where to put jk logs
JkLogFile     /var/log/apache2/mod_jk.log
# Set the jk log level [debug/error/info]
JkLogLevel    info
# Select the timestamp log format
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "

# Define the vhost that'll expose Tomcat
<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	ServerName local-alfresco

        JkMount /status     jkstatus
        JkMount /alfresco   jkalfresco
        JkMount /alfresco/* jkalfresco
        JkMount /share      jkshare
        JkMount /share/*    jkshare
The ServerName needs to be unique, so either make it your machine name (only vhost), or create a new alias for in your /etc/hosts file and use that. Restart apache, and try visiting [server name]/status to check that mod_jk is loaded and running. Then, visit [server name]/alfresco and check that you get explorer as guest.

Finally, before we consider about how it all fits together, we need to turn on Authentication in Apache, so it has some auth to pass to Tomcat. Full details on this are in the Apache docs, but basically you'll want to use htpasswd to create a digest password file, something like:
  $ # Create the file, add admin
  $ htpasswd -c /etc/apache2/conf.other/alfresco.htpasswd admin admin
  $ # Create another user (don't use these passwords in production!)
  $ htpasswd /etc/apache2/conf.other/alfresco.htpasswd nick password

Now, add to our vhost some auth lines:
        <Location />
          AuthType Basic
          AuthName "Test Alfresco Access Auth"
          AuthBasicProvider file
          AuthUserFile /etc/apache2/conf.other/alfresco.htpasswd
          Require valid-user
Restart Apache, and re-visit [server name]/alfresco . You should be prompted for a password. Specify admin and admin (as given to htpasswd), and you should be taken to Explorer logged in as admin automatically. We're basically there!

At this point, make sure you're only using a test server, have suitable firewalling etc in place. We're just trying to get some basics in place to test with, so we have a working base before making our changes. More is needed to make this production...

So, how does this all work? Apache is doing the authentication, and passing that through to Tomcat. We can see that Tomcat sees the username by creating a small test jsp somewhere in the Alfresco webapp, containing:
User: <%=request.getRemoteUser()%>
Run that, and you'll see the username you gave to the Apache auth come through. That username is passed into the Servlet Request.

What does Alfresco do with it? When we enabled our External Authentication SubSystem, we effectively pulled in the external-filter-context.xml context file, along with partly overriding the default The webscriptAuthenticationFilter isn't really doing that much for us, the key bit is the remoteUserMapper. This is configured up to use DefaultRemoteUserMapper

When the request comes in to Alfresco, for either Explorer or /wcservices/ (but not /services/ as that's different), the global Authentication Filter fires. In doFilter it calls getSessionUser which in turn checks for a Remote User Mapper. If one is found (and we've enabled one with our next Authentication SubSystem), getRemoteUser is called. If you look at that method in DefaultRemoteUserMapper, we'll see it checks the Remote User on the request, and returns that. So, no login is needed at the Tomcat / Alfresco layer, because the value from Apache is trusted.

We can also see how we could do http header auth at this point if we wanted, and also see some of the security issues which mean you shouldn't blindly enable this in production...!

So, we can now use our test Apache auth setup to be auto logged-in to Alfresco Explorer and wcservices. In the next part, we'll see how we enable Share auto-login with the same setup, and how that actually works under the hood.