Getting Started Spring Content with RBAC

What you'll build

We'll build on the previous guide Getting Started with Spring Content REST API.

What you'll need

  • About 30 minutes

  • A favorite text editor or IDE

  • JDK 1.8 or later

  • Maven 3.0+

How to complete this guide

Before we begin let's set up our development environment:

  • Download and unzip the source repository for this guide, or clone it using Git: git clone https://github.com/paulcwarren/spring-content-gettingstarted.git

  • We are going to start where Getting Started with Spring Content REST API leaves off so cd into spring-content-gettingstarted/spring-content-rest/complete

When you’re finished, you can check your results against the code in spring-content-gettingstarted/spring-content-with-rbac/complete.

Update dependencies

Add org.springframework.boot:spring-boot-starter-security dependencies.

pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
    </parent>

    <artifactId>spring-content-with-rbac</artifactId>

    <properties>
        <java.version>1.8</java.version>
        <ginkgo-version>1.0.14</ginkgo-version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.paulcwarren</groupId>
            <artifactId>spring-content-fs-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.paulcwarren</groupId>
            <artifactId>spring-content-rest-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
        </dependency>

        <!-- Test dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>2.9.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.paulcwarren</groupId>
            <artifactId>ginkgo4j</artifactId>
            <version>${ginkgo-version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Add Security Constraints

Enable web security.


package gettingstarted;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.User.UserBuilder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

	@Bean
	public InMemoryUserDetailsManager userDetailsManager() {

		UserBuilder builder = User.withDefaultPasswordEncoder();

		UserDetails eric = builder.username("eric").password("wimp").roles("READER").build();
		UserDetails paul = builder.username("paul").password("warren").roles("READER", "AUTHOR").build();

		return new InMemoryUserDetailsManager(eric, paul);
	}

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/files/**/content").hasRole("READER")
                .antMatchers(HttpMethod.PUT, "/files/**/content").hasRole("AUTHOR")
                .anyRequest().permitAll()
                .and().httpBasic()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    }
}

First, define two users and two roles. Eric is a content reader and Paul is a content reader and author.

Second, secure the Spring Content endpoint /files/**/content that GETs and SETs content to the reader and author roles respectively.

@PreAuthorize

As an alternative secure removing content to the author role by using an alternative approach using the @PreAuthorize annotation on the FileContentStore's unset method.


package gettingstarted;

import org.springframework.content.commons.property.PropertyPath;
import org.springframework.content.commons.repository.ContentStore;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;

@Component  // just to keep the ide happy!
public interface FileContentStore extends ContentStore<File, String> {

	@Override
	@PreAuthorize("hasRole('ROLE_AUTHOR')")
	public File unsetContent(File file, PropertyPath propertyPath);

}

Build an executable JAR

If you are using Maven, you can run the application using mvn spring-boot:run. Or you can build the JAR file with mvn clean package and run the JAR by typing:

java -jar target/gettingstarted-spring-content-with-rbac-0.0.1.jar

Create an Entity and version it

Create an entity:

curl -X POST -H 'Content-Type:application/hal+json' -d '{}' http://localhost:8080/files/

Attempt to associate content as Eric:

curl -u eric:wimp -X PUT -H 'Content-Type:text/plain' -d 'Hello Spring Content with RBAC World!' http://localhost:8080/files/1/content

Associate content as Paul:

curl -u paul:warren -X PUT -H 'Content-Type:text/plain' -d 'Hello Spring Content with RBAC World!' http://localhost:8080/files/1/content

Fetch the content as Eric:

curl -u eric:wimp -H 'Accept:text/plain' http://localhost:8080/files/1/content

And as Paul:

curl -u paul:warren -H 'Accept:text/plain' http://localhost:8080/files/1/content

Attempt to delete content as Eric:

curl -u eric:wimp -X DELETE http://localhost:8080/files/1/content

Delete content as Paul:

curl -u paul:warren -X DELETE http://localhost:8080/files/1/content

Summary

Congratulations! You've written a simple application that uses Spring Content secured with role-based access control.

Don't forget you can simply change the type of the spring-content bootstarter project on the classpath to switch from file storage to a different storage technology.

Spring Content supports the following implementations:-

  • Spring Content Filesystem; stores content as Files on the Filesystem (as used in this tutorial)

  • Spring Content S3; stores content as Objects in Amazon S3

  • Spring Content JPA; stores content as BLOBs in the database

  • Spring Content MongoDB; stores content as Resources in Mongo's GridFS