Authenticate against a hippo repository using spring security
Within a number of my projects we use Hippo to create a website. Hippo contains a JackRabbit repository that has capabilities for authenticating and authorizing users. Hippo builds on this functionality for its own security model. In most of these projects we create an integration component to store content in the repository from other systems and retrieve content from the repository to be useful in other applications. The integration component contains a web interface created using spring-mvc. We use spring-security to secure the web application. Since we do not want to maintain a separate list of users for the integration application we want to authenticate against the hippo repository. In my current project we have a lot of users that do not need access to the integration web application. We want to reuse the security domains functionality of Hippo to authorize the users.
In this blog post I am going to explain the different parts of the solution in such a way that you can use the solution in your own project.
We have two different configurations. We need to configure the web application containing the integration solution. We also need to configure the repository to create the node with a security rule that limits access to only users in a certain group.
Configure Spring
I am not going to explain how spring-mvc works. I do want to show the most important parts of the spring-security configuration. The next block shows the urls that are secured and some other required beans.
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd"> <context:component-scan base-package="nl.uva.hippo.integration.web.security"/> <http auto-config='true' use-expressions="true"> <intercept-url pattern="/presentation/**" filters="none"/> <intercept-url pattern="/login.jsp" filters="none"/> <intercept-url pattern="/repository/**" filters="none"/> <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/> <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1"/> </http> <authentication-manager> <authentication-provider ref="hippoAuthenticationProvider"/> </authentication-manager> <util:properties location="file:///${CONFIG_DIR}/repository-connector.properties" id="hippoRepoConfig"/> </beans:beans>
As you can see, we use http security and we configure a few url’s that do not require any authentication (filters=none). The last intercept-url is interesting, this is where we configure that only users with the ROLE_USER can login.
The next bean is the authentication-manager. Here we configure the authentication-provider that I will show next.
Authentication Provider
The authentication provider must implement org.springframework.security.authentication.AuthenticationProvider. We’ll concentrate on the authenticate method. Let us have a look at the code first.
public Authentication authenticate(Authentication authentication) { if (!supports(authentication.getClass())) { return null; } UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication; String username = token.getName(); String password = String.valueOf(token.getCredentials()); try { HippoRepository repository = HippoRepositoryFactory.getHippoRepository(repositoryLocation); Session session = repository.login(username, password.toCharArray()); session.getNode(securityPath); } catch (LoginException e) { throw new BadCredentialsException("Invalid username and/or password", e); } catch (RepositoryException e) { throw new AuthenticationServiceException("Credentials could not be verified", e); } UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(username, authentication.getCredentials(), USERAUTHORITIES); result.setDetails(authentication.getDetails()); return result; }
First we distill the username and the password from the provided authentication token. Then we login with these credentials in the repository. With the session that we get back as a result we obtain the path to a special node that we have secured using security domains. If one of these steps fail, authentication does not work. If both are fine, you can enter the application.
Authenticating users
Users that are available in the repository can authenticate. Authentication in our case means you get back an authenticated session for the user with the provided credentials.
Authorizing users
Authorizing means you are able to obtain a special configured node. Special configuration means we put a security domain on this node. If you do not have access to this node, you are not authorized to enter the application.
Security domains
We need to add a security domain on the node. In our example we only want admins to acces the application.
You can read the documentation here:
http://www.onehippo.org/cms7/documentation/user/administrators/concepts/domains.html
https://wiki.onehippo.com/display/CMS7/Repository+Authorization+and+Permissions
/hippo:configuration/hippo:frontend contains the applications that connect to the repository. Entries are available for the cms, the login and the console. We have added a node for the integration component. The name for the node is integration and the primary type of the node is: frontend:application. The following code block shows the exported xml.
<sv:node sv:name="integration" xmlns:sv="http://www.jcp.org/jcr/sv/1.0"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>frontend:application</sv:value> </sv:property> </sv:node>
/hippo:configuration/hippo:domains/frontendconfig/frontend-application contains nodes to exclude default access to your application nodes. If you do not omit access to applications here everybody can still access your application. This is done to make sure you can see the login form application.
<sv:node sv:name="exclude-integration" xmlns:sv="http://www.jcp.org/jcr/sv/1.0"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:facetrule</sv:value> </sv:property> <sv:property sv:name="hipposys:equals" sv:type="Boolean"> <sv:value>false</sv:value> </sv:property> <sv:property sv:name="hipposys:facet" sv:type="String"> <sv:value>nodename</sv:value> </sv:property> <sv:property sv:name="hipposys:filter" sv:type="Boolean"> <sv:value>false</sv:value> </sv:property> <sv:property sv:name="hipposys:type" sv:type="String"> <sv:value>Name</sv:value> </sv:property> <sv:property sv:name="hipposys:value" sv:type="String"> <sv:value>integration</sv:value> </sv:property> </sv:node>
The final part is to secure the node that is obtained when authenticating. We need to set a security rule on the node. This xml is a bit longer. It contains multiple nodes. First we create the node hipposys:domain, this node contains the other nodes. The other nodes are:
- hipposys:authrole with the read only groups. Empty in our case.
- The admin-authrole node (also a hipposys:authrole), this gives the admin role to three groups: admin, algemeen-applicatiebeheer and algemeen-functioneel-beheer.
- A domain rule that contains a match for a node of type frontend:application and the name integration.
<sv:node sv:name="integration-acl" xmlns:sv="http://www.jcp.org/jcr/sv/1.0"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:domain</sv:value> </sv:property> <sv:node sv:name="hippo:authrole"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:authrole</sv:value> </sv:property> <sv:property sv:multiple="true" sv:name="hipposys:groups" sv:type="String"/> <sv:property sv:name="hipposys:role" sv:type="String"> <sv:value>readonly</sv:value> </sv:property> </sv:node> <sv:node sv:name="admin-authrole"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:authrole</sv:value> </sv:property> <sv:property sv:multiple="true" sv:name="hipposys:groups" sv:type="String"> <sv:value>admin</sv:value> <sv:value>algemeen-applicatiebeheer</sv:value> <sv:value>algemeen-functioneel-beheer</sv:value> </sv:property> <sv:property sv:name="hipposys:role" sv:type="String"> <sv:value>admin</sv:value> </sv:property> </sv:node> <sv:node sv:name="integration-frontend-application"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:domainrule</sv:value> </sv:property> <sv:node sv:name="type-frontend-application"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:facetrule</sv:value> </sv:property> <sv:property sv:name="hipposys:equals" sv:type="Boolean"> <sv:value>true</sv:value> </sv:property> <sv:property sv:name="hipposys:facet" sv:type="String"> <sv:value>jcr:primaryType</sv:value> </sv:property> <sv:property sv:name="hipposys:filter" sv:type="Boolean"> <sv:value>false</sv:value> </sv:property> <sv:property sv:name="hipposys:type" sv:type="String"> <sv:value>Name</sv:value> </sv:property> <sv:property sv:name="hipposys:value" sv:type="String"> <sv:value>frontend:application</sv:value> </sv:property> </sv:node> <sv:node sv:name="match-integration"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:facetrule</sv:value> </sv:property> <sv:property sv:name="hipposys:equals" sv:type="Boolean"> <sv:value>true</sv:value> </sv:property> <sv:property sv:name="hipposys:facet" sv:type="String"> <sv:value>nodename</sv:value> </sv:property> <sv:property sv:name="hipposys:filter" sv:type="Boolean"> <sv:value>false</sv:value> </sv:property> <sv:property sv:name="hipposys:type" sv:type="String"> <sv:value>Name</sv:value> </sv:property> <sv:property sv:name="hipposys:value" sv:type="String"> <sv:value>integration</sv:value> </sv:property> </sv:node> </sv:node> </sv:node>
Together this results in users that do not get the admin role that are not able to login to the integration application.