User authentication is core of any application; it can be a desktop application, a web application or a web service. CQ is a tool that is mainly used for building component based web sites and provides content management capabilities. The underlying frameworks Apache Felix and Sling together provide the foundation for CQ’s authentication capabilities.
Let’s explore the web authentication in brief and then I’ll try to explain the CQ’s authentication in detail. Authentication for web application mainly works on request/response headers, session and cookies.
Request/response based authentication: When a page is requested from a web server, the server looks for some specific information in header (to be specific in request “Authorization” header) and if information is available in header then that information is used by server to validate a user. So Authorization header can contain the credentials in Basic form or Digested form. The basic authentication header has following format:
Authorization: Basic username:password (encrypted in Base64 encoding)
There are few other Basic schemes that are used by some sites, some examples are:
a) User/Password scheme
Authorization: user fred:mypassword
Authorization: kerberos kerberosauthenticationsparameters
Once the authorization header information is received by server, it’s the responsibility of server to decode the credentials and validate it based on the Authorization scheme. If the request is authenticated on server then a 200 (OK) response is sent back to browser and subsequent request uses the same header information for validating user requests. If a request can not be authenticated on server because of invalid information in Authorization header, the server sends back a 401/Unauthorized response back and user is present a login form to enter username/password.
Path based restriction for header authorization: Let’s say we have a web application that is structured as
/a/b/c/ and /a/d/c/. When a user is trying to access a resource that is under /a/b/c path, they need to enter valid user name and password so that server can validate the provided information and will cache it in request Authorization header. Now, user is authenticated to access any resource under /a/b/c but, when user will try to access anything under /a/d/c/ the authorization information in header (cached for /a/b/c/ path) will not be used by server and a 401/Unautorized response is sent back to browser (and a form will be presented to user where user can enter user name and password). So authorization headers are path specific.
Request header based authorization does not deals with cookies or session, the authorization information is cached by browsers and is available till the browser is not closed or a user does not forcibly clears the header information (active logins)
NOTE: Authorization header is a request header (and NOT a response header). We can (and should) not explicitly set this header.
Session based authentication: When a resource is requested from server a unique id (called as session ID) is generated by server and is maintained on the server (something like JSESSIONID=053d59e1-3398-cd4a-8a12-61d371f9f120). For any subsequent requests server checks that whether it already contains a session ID for the received request or not? If a session ID is already there then the same session ID will be used. We can store arbitrary data/objects in session. When user provides a valid user name and password then application validates it and store some information in session to indicate that the user has already provided credentials and is authorized to access resources on server. If a user has not requested any resource for more than a timeout setting value from server then that user is treated as inactive and the his/her session (and data in it) is destroyed. Almost all servers have a setting for timeout value that can be configured based on the requirement to invalidate sessions. The main problem that we have with session is that sometimes it become difficult to replicate user session (on different servers) in clustered environment and authentication does not work properly but, its a deployment issue and there are various ways to fix it (like sticky session at load balancer level etc.)
Before actually implementing any of the available authentication scheme/mechanism it is important to consider following things:
1) Who are our end users? Do they need to provide credentials for accessing resources on server?
2) What kind of application we are developing? What APIs and framework we are using?
3) Is it a secure site and the credentials should be encrypted before transmitting over wire?
4) What kind of deployment environment we have in production? Is it a clustered environment with a load balancer? Do we have session replication mechanism in place if we are planning to deploy same application on multiple servers?
Based on type of content security, available resource and deployment environment we need to judge which combination fits well to our need.
We just explored the various ways of authenticating a user on server now let’s explore the CQ’s authentication implementation and how to develop our own site using CQ’s underlying frameworks (Sling and Felix). As I explained in my earlier post Understanding Day's CQ & Underlying frameworks - Part 1, Sling is a REST based web framework and Felix is and OSGI specification implementation and the authentication in CQ works little differently as compared to traditional applications. In traditional application we normally have a controller (at entry point of our application) that validates user but, in CQ a request goes through chain of authenticators exposed as OSGI services (CQ’s own authenticator and any custom authenticator that we have) for validating user.
Also, as I have mentioned in my earlier posts that CQ uses JCR (CRX) for storing data (files, images etc.) and user’s information. If an user want to access something on server (basically from JCR) then either they need to provide credentials (if the requested resource is secured using ACL) or they can access it directly if the resource does not have any ACL (access control list restrictions). So there are basically two types of users we can have in JCR:
a) Anonymous: an anonymous user can only access unsecured/public resources and does not require any credentials.
b) Registered: If a user wants to access secured/private resources on server then they must be registered on JCR and need to provide their credentials. JCR validates the provided credentials against the user account data/information stored in JCR repository and creates a JCR session (note that it is not HTTP session) and a resource resolver. I’ll cover the session and resource resolver later in this post.
Following are few main interfaces/classes that we need to explore for understanding Sling’s authentication framework:
1) Authenticator (Interface)
This is an interfaces that defines basic login() and logout() methods that is implemented by an actual Authenticator class (e.g. SlingAuthenticator). What and how to implement login and logout functionality is complete responsibility of class that implements this interface.
2) AuthenticationSupport (Interface): This interface defines handleSecurity() method which must be implemented class that is exposed as an Authenticator. Sling framework internally calls the handleSecurity() method for extracting the user credentials and returns a Boolean flag depending up on whether user validation is successful or failed.
3) SlingAuthenticator (class): This class implements Authenticator and AuthenticationSupport interfaces (see label 1 in below diagram) and is registered as an OSGI service with Felix. This class is foundation for Authentication in Sling, apart from implementing the methods from Authenticator and AuthenticationSupport class this class holds a mapping between request paths and corresponding authentication handlers (a class that implements AuthenticationHandler interface) that will be used for authenticating a request. When a user request for a resource from server, sling authenticator extracts the request path from request and it’ll try to find whether there is any authentication handler that is mapped for the path (see label 2 & 4 in below diagram), if an authentication handler is mapped for the requested path then the authentication control is delegated to authentication handler class.
4) AuthenticationHandler (Interface): This interface defines extractCredentials(), requestCredentials() and dropCredentials() methods (see label 5 in below diagram) that must be implement by an Authentication Handler implementation class that we need to register/map as authentication handler with SlingAuthenticator service. Once Sling authentication has selected an authentication handler based on request path and deligated authentication job to a particular authentication handler implementation class then, following methods are called:
a. extractCredentials(): Based on the authentication scheme that we want to implement (Authorization header based authentication, session based authentication or cookie based authentication) the implementation class should implement this method to extract credentials from (header, session or cookies) and must one of the following:
i. AuthenticationInfo: If authentication is successful then this method must return an AuthenticationInfo object (that holds the authentication scheme used, user name and password) to SlingAuthenticator. If valid AuthenticationInfo object is returned by this method then sling authenticator will NOT execute any other Authentication Handler that is in the list after the current authentication handler.
ii. Null: If authentication failed then this method must return a null. When a null value is returned from this method, sling authenticator will try the next authentication handler that is mapped for this path (if any).
iii. AuthenticationInfo.DOING_AUTH: if authentication is failed or because of some reason we want to redirect request to some other page (that can be a login page or any other resource) then this method must return DOING_AUTH to sling authenticator. When sling authenticator receives a DOING_AUTH as return value then it stops there and request is suspended.
b. requestCredentials(): An authentication handler that implements AuthenticationHandler interface must implement this method for rendering a login form. If validation fails and sling framework wants to ask for user name and password in that case sling authenticator will call this method and users will be presented a login form to enter user name and password.
c. dropCredentials(): An authentication handler that implements AuthenticationHandler interface should implement this method to removed user credentials (from either authorization header, session or cookies). When a user logs out, sling authenticator calls this method to remove/clean user credentials.
Once all authentication handler have been executed and no one responded back with a valid credentials (AuthenticationInfo), then sling authenticator will try to see if anonymous user can access the requested resource (see label 3 in below diagram).
5) AbstractHTTPAuthHandler (abstract class): This is an abstract class that has basic authentication handler implementation. This class implements Basic Authorization scheme with base 64 encoding of username and password. This class has basic implementation of extractCredentials(), requestCredentials() and dropCredentials() methods (see label 6 & 7 in below in diagram) and provides an abstract method getLoginPage() that can be implemented by child classes to return a custom login page.
6) HTTPAuthHandler (class): This class extends AbstractHTTPAuthHandler and implements Basic Authorization (see label 9 & 10 in below diagram) and CUG support) and is mapped for root path (/) i.e. by default this is the authentication handler that will be called by Sling Authenticator.
UML representation of Sling/CQ Authentication Implementation
The AuthenticationInfo class
For more information on sling authentication you can refer http://sling.apache.org/site/authentication-framework.html