Fork me on GitHub

Simple WebDAV Advanced - Security Framework Integration

You can optionally integrate the SimpleWebdavServlet with a Web Application Security Framework such as Spring Security Framework. SimpleWebdavServlet allows you to override #getCredentialsProvider() method which gives a org.apache.jackrabbit.server.CredentialsProvider returning a valid javax.jcr.Credentials object for the WebDAV session and handles the basic authentication payloads by default if necessary.

Therefore, if you set a servlet filter provided by a Web Application Security Framework before the SimpleWebdavServlet, then it is technically possible to read the authentication information, established by the Web Application Security Framework, and return a javax.jcr.Credentials object from your custom CredentialsProvider. If SimpleWebdavServlet finds a valid javax.jcr.Credentials object from your custom CredentialsProvider, then it won't ask for basic authentication any more.

Example Scenario with Spring Security Framework

This example is only for explanation purpose, not meant to be used directly in production. You should understand more about the underlying Web Application Security Framework and your own requirements and architectural concerns.

Suppose you configured Spring Security Framework in the web application and so a servlet filter provided by the Spring Security Framework handles authentication before SimpleWebdavServlet, with the following example configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
       xmlns:beans="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                           http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd">

  <http auto-config="true"
        use-expressions="true"
        realm="Hippo CMS WebDAV">
    <csrf disabled="true"/>
    <intercept-url pattern="/webdav/" access="permitAll" />
    <intercept-url pattern="/webdav/default/webfiles/**" access="isAuthenticated()" />
    <intercept-url pattern="/webdav/default/**" access="denyAll" />
    <http-basic />
  </http>

  <authentication-manager>
    <authentication-provider ref="hippoRepositoryAuthenticationProvider" />
  </authentication-manager>

  <beans:bean id="hippoRepositoryAuthenticationProvider"
              class="com.example.security.spring.HippoRepositoryAuthenticationProvider">
  </beans:bean>

</beans:beans>
      

The HippoRepositoryAuthenticationProvider can be implemented like the following (as a simplified version):

public class HippoRepositoryAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    private String repositoryAddress = "vm://";
    private HippoRepository repository;

    public HippoRepository getRepository() throws RepositoryException {
        if (repository == null) {
            repository = HippoRepositoryFactory.getHippoRepository(repositoryAddress);
        }
        return repo;
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // SNIP
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        Session session = null;
        final String password = authentication.getCredentials().toString();
        final Credentials credentials = new SimpleCredentials(username, password.toCharArray());

        try {
            session = getRepository().login(credentials);
        } catch (LoginException e) {
            throw new BadCredentialsException(e.getMessage());
        } catch (RepositoryException e) {
            throw new ProviderNotFoundException(e.getMessage());
        } finally {
            try {
                session.logout();
            } catch (Exception ignore) {
            }
        }

        final Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new HippoRepositoryUser(username, password, authorities, credentials);
    }

    public static class HippoRepositoryUser extends User {
        private final Credentials credentials;

        public HippoRepositoryUser(String username, String password, Collection<? extends GrantedAuthority> authorities,
                Credentials credentials) {
            super(username, password, authorities);
            this.credentials = credentials;
        }

        public Credentials getCredentials() {
            return credentials;
        }
    }
}
      

By the above configuratin and bean definition, Spring Security Framework will be able to handle authentication and possibly it may keep the authentication state for the next servlet chain including SimpleWebdavServlet.

Now, let's extend SimpleWebdavServlet to use the stored HippoRepositoryUser data without having to handle basic authentication again:

public class MyCustomSimpleWebdavServlet extends SimpleWebdavServlet {

    @Override
    protected CredentialsProvider getCredentialsProvider() {
        return new CredentialsProvider() {
            @Override
            public Credentials getCredentials(HttpServletRequest request) throws LoginException, ServletException {
                final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication == null) {
                    throw new LoginException("Not authenticated.");
                }
                final Object user = authentication.getPrincipal();
                if (!(user instanceof HippoRepositoryUser)) {
                    throw new LoginException("Not authenticated.");
                }

                // Return credentials from Spring Security Framework's AuthenticationProvider
                // without having to proceed with custom basic authentication again.
                return ((HippoRepositoryUser) user).getCredentials();
            }
        };
    }
}
      

Now, you can replace the servlet definition with your custom class extending SimpleWebdavServlet in the web.xml:


  <!-- SNIP -->

  <!--
    WebDAV support (DAV 1,2 and DeltaV) to your jackrabbit repository.
  -->
  <servlet>
    <servlet-name>SimpleWebdavServlet</servlet-name>
    <servlet-class>com.example.webdav.MyCustomSimpleWebdavServlet</servlet-class>
    <!-- SNIP -->
  </servlet>

  <!-- SNIP -->

  <!--
    WebDAV support (DAV 1,2 and DeltaV) Servlet Mapping:
  -->
  <servlet-mapping>
    <servlet-name>SimpleWebdavServlet</servlet-name>
    <url-pattern>/webdav/*</url-pattern>
  </servlet-mapping>

  <!-- SNIP -->