group running

4 Ways To Control Access to Spring Data REST

4 Ways To Control Access to Spring Data REST

This post looks at 4 ways to control access to Spring Data REST using RepositoryDetectionStrategies’s

This post forms part of a series looking at Spring Data REST –

RepositoryDetectionStrategies

You can control access to Spring Data REST using the 4 RepositoryDetectionStrategies –

Field

ALL

Description

Exposes all Spring Data Repositories regardless of annotations or access control

ANNOTATED

Only Repositories exposed with RepositoryRestResource or RestResource, and the exported flag is not set to false.

DEFAULT

public Spring Data Repositories or ones annotated with RepositoryRestResource

VISIBILITY

Only public Spring Data Repositories

Source Code

You can run the examples by –

mvnw spring-boot:run

Examples

Ive created a Spring Rest Configuration class for this example –

@Component
public class SpringRestConfiguration extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.DEFAULT);
}
}

All of the examples will be changing the enumerated value of RepositoryDetectionStrategy.RepositoryDetectionStrategies, and testing what REST end points are exposed.

I have also annoted one of our Spring Data Repositories –

@RepositoryRestResource
@PreAuthorize("hasRole('ROLE_USER')")
public interface ParkrunCourseRepository extends CrudRepository<ParkrunCourse, Long> {
@Override
@PreAuthorize("hasRole('ROLE_ADMIN')")
ParkrunCourse save(ParkrunCourse parkrunCourse);
}

RepositoryDetectionStrategies.DEFAULT

A default setting RepositoryDetectionStrategy detect –

  • public interfaces
  • annotated with RepositoryRestResource or RestResource

So with our example we would expect to see both interfaces exposed as REST end points

We can confirm this by calling http://localhost:8080/rest/profile –

curl -u user:user -X GET http://localhost:8080/rest/profile
{
 "_links" : {
 "self" : {
 "href" : "http://localhost:8080/rest/profile"
 },
 "secrets" : {
 "href" : "http://localhost:8080/rest/profile/secrets"
 },
 "parkrunCourses" : {
 "href" : "http://localhost:8080/rest/profile/parkrunCourses"
 }
 }
}

RepositoryDetectionStrategies.ANNOTATED

Annotated will only detect interfaces annotated with RepositoryRestResource or RestResource, and an exported flag that is true(default). In our test case we would only expect to see the end point for ParkrunCourseRepository to be exposed as it is annotated with @RepositoryRestResource

We can test and confirm this –

curl -u user:user -X GET http://localhost:8080/rest/profile
{
 "_links" : {
 "self" : {
 "href" : "http://localhost:8080/rest/profile"
 },
 "parkrunCourses" : {
 "href" : "http://localhost:8080/rest/profile/parkrunCourses"
 }
 }
}

RepositoryDetectionStrategies.VISIBILITY

VISIBILITY will only detect REST and expose reportories based if they are publicly exposed.

Lets make a change to SecretRepository to leave the interface with default visibility –

interface SecretRepository extends CrudRepository<Secret, Long> {
}

We would not expect to see this end point, and would only expect to see the ParkrunCourseRepository end point. This is confirmed when we call the rest/profile –

curl -u user:user -X GET http://localhost:8080/rest/profile
{
 "_links" : {
 "self" : {
 "href" : "http://localhost:8080/rest/profile"
 },
 "parkrunCourses" : {
 "href" : "http://localhost:8080/rest/profile/parkrunCourses"
 }
 }
}

RepositoryDetectionStrategies.ALL

Finally if we use RepositoriesDetectionStrategies.ALL with SecretRepository at default access. Then we would expect to see both the parkrunCourses and Secret’s end point. All will also expose end points that have an exported false attribute.

Restart the server and test –

curl -u user:user -X GET http://localhost:8080/rest/profile
{
 "_links" : {
 "self" : {
 "href" : "http://localhost:8080/rest/profile"
 },
 "parkrunCourses" : {
 "href" : "http://localhost:8080/rest/profile/parkrunCourses"
 },
 "secrets" : {
 "href" : "http://localhost:8080/rest/profile/secrets"
 }
 }
}

Conclusions

This post looked at 4 ways to control access to Spring Data REST using RepositoryDetectionStrategies allow or restrict access to the underlying Spring Data JPA repositories. The options are – ALL, ANNOTATED, VISIBILITY or DEFAULT

Thanks to Mayra Carreno @ https://unsplash.com/@mayracarreno?utm_campaign=photographer-credit

Data Hiding using JsonIgnore and Spring Data JPA

Data Hiding using JsonIgnore and Spring Data JPA

Data Hiding using JsonIgnore and Spring Data JPA is achieved using two approaches –

  • @JsonIgnore and @JsonIgnoreProperties
  • Repository Detection Strategies

This post considers @JsonIgnore and @JsonIgnoreProperties

Code

The code is available at –

Code Changes

I’ve added an extra table to for this example –

@Entity
public class Secrets {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String mySecrets;
public String getMySecrets() {
return mySecrets;
}
public void setMySecrets(String mySecrets) {
this.mySecrets = mySecrets;
}
}

With its associated repository –

@PreAuthorize("hasRole('ROLE_USER')")
public interface SecretsRepository extends CrudRepository<Secrets, Long> {
}

Running The Code

I have left the security from the last tutorial, Securing Spring Data REST with PreAuthorize, in place – but we can run this code using –

mvnw spring-boot:run

We can then call rest/profile to see the two exposed repositories –

curl -u user:user -X GET http://localhost:8080/rest/profile
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/rest/profile"
},
"secrets" : {
"href" : "http://localhost:8080/rest/profile/secrets"
},
"parkrunCourses" : {
"href" : "http://localhost:8080/rest/profile/parkrunCourses"
}
}
}

And calling the secrets REST end point-

curl -u user:user -X GET http://localhost:8080/rest/secrets/1
{
"mySecret" : "I want to hide this",
"_links" : {
"self" : {
"href" : "http://localhost:8080/rest/secrets/1"
},
"secret" : {
"href" : "http://localhost:8080/rest/secrets/1"
}
}
}

This posts looks at techniques I can use to not expose the SecretRepository

@JsonIgnore and @JsonIgnoreProperties

The purpose of @JsonIgnore, and @JsonIgnoreProperties is to hide attributes from the Jackson parser by instructing it to Ignore these fields

Usage is simply a matter of tagging the attribute with the @JsonIgnore

@Entity
public class Secret {
//
@JsonIgnore
private String mySecret;
//
}

Or we can achieve the same using @JsonIgnoreProperties annotation –

@JsonIgnoreProperties({"mySecret"})
@Entity
public class Secret {
//
private String mySecret;
//
}

With either of these changes we can then call our secrets REST end point, and the mySecret field is no longer exposed –

curl -u user:user -X GET http://localhost:8080/rest/secrets/1
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/rest/secrets/1"
},
"secret" : {
"href" : "http://localhost:8080/rest/secrets/1"
}
}
}

Conclusion

@JsonIgnore or @JsonIgnoreProperties simply hides the field from the Jackson parser. This is good for hiding small pieces of information. The downside is we still have an exposed end point due to the default Repository Detection Strategies.

 

 

Securing Spring Data REST with PreAuthorize

Securing Spring Data REST with PreAuthorize is an alternative method to securing Spring Data REST API’s, building on the previous apporach covered in Spring Security and Spring Data REST.

Securing Spring Data REST with Preauthorize

Securing Spring Data REST with PreAuthorize code is available on github –

https://github.com/farrelmr/introtospringdatarest/tree/3.0.0

https://github.com/farrelmr/introtospringdatarest/releases/tag/3.0.0

Run the code by typing –

mvnw spring-boot:run

Maven

In order to use the preAuthorise attributes you need to import spring-boot-security –

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

SecurityConfig

The key points are –

  • Maintain security for roles “ADMIN” and “USER” on “/rest/**” requests
  • @EnableGlobalMethodSecurity(prePostEnabled = true) – this enables the annotations on the JPA model
package com.javabullets.springdata.jparest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.annotation.method.configuration.EnableGlobalMethodSecurity;

import org.springframework.http.HttpMethod;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth)
			throws Exception {
		auth.
			inMemoryAuthentication()
				.withUser("user").password("user").roles("USER").and()
				.withUser("admin").password("admin").roles("USER","ADMIN");
	}

    @Override
    protected void configure(HttpSecurity http) throws Exception {
	    http
          .authorizeRequests()
            .antMatchers("/rest/**").hasAnyRole("ADMIN","USER").and()
          .httpBasic()
            .and()
		   .csrf().disable();		   
    }
}

ParkrunCourseRepository

The key changes are the use of PreAuthorize on the ParkrunCourseRepository –

package com.javabullets.springdata.jparest;

import org.springframework.data.repository.CrudRepository;
import org.springframework.security.access.prepost.PreAuthorize;

@PreAuthorize("hasRole('ROLE_USER')")
public interface ParkrunCourseRepository extends CrudRepository<ParkrunCourse, Long> {
	@Override
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	ParkrunCourse save(ParkrunCourse parkrunCourse);
}

The key points are –

  • Use of hasRole – note you need to append ROLE_ to your roles
  • We could also add PreAuthorize security to custom methods defined in the repository

Putting It Together

mvnw spring-boot:run

POST methods – Access Forbidden

curl -u user:user -X POST -H "Content-Type:application/json" -d "{  \"courseName\" : \"adminOnly\",  \"url\" : \"url\",  \"averageTime\" : \"10000\" }" http://localhost:8080/rest/parkrunCourses

{"timestamp":1496134011261,"status":403,"error":"Forbidden","message":"Access is denied","path":"/rest/parkrunCourses"}

POST methods – Access Allowed

curl -u admin:admin -X POST -H "Content-Type:application/json" -d "{  \"courseName\" : \"adminOnly\",  \"url\" : \"url\",  \"averageTime\" : \"10000\" }" http://localhost:8080/rest/parkrunCourses

{
  "courseName" : "adminOnly",
  "url" : "url",
  "averageTime" : 10000,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/rest/parkrunCourses/13"
    },
    "parkrunCourse" : {
      "href" : "http://localhost:8080/rest/parkrunCourses/13"
    }
  }
}

Discussion

This post shows a different approach to role based security of Spring Data REST with PreAuthorize. It is not a question of which method is better, but which is practical. It may be that you cannot use preAuthorize in your codebase, but can use role based authentication as outlined in Spring Security and Spring Data REST

From a design point of view you may want to define a custom repository with your core PreAuthorize rules, allowing your model to inherit security.

Conclusions

This post shows how Spring Security and Spring Data REST can be combined to secure REST API URL’s and HTTP methods. It used a basic form of Spring authentication, combining a MemoryRealm with the security configuration. We have also demonstrated how to restrict access to REST methods based on user group.

My next post will look at how Spring Data REST can restrict access by deciding what methods it exposes, and what fields are exposed

Spring Security and Spring Data REST

Spring Security and Spring Data REST are discussed in this post. It builds on the previous post “Introduction to Spring Data REST” and my series of posts on Spring Security –

Spring Security and Spring Data REST

Spring Security is split into two components –

  • Authentication – Defined by AuthenticationManager, or the source of the authentication credentials
  • Authorisation – what we want to protect – URL’s, Roles, method

This tutorial is only considering BasicAuthentication with an memory realm. I will probably return to this subject to show how to properly harden a RESTful API.

Source Code

Code available on github –

https://github.com/farrelmr/introtospringdatarest/tree/2.0.0

https://github.com/farrelmr/introtospringdatarest/releases/tag/2.0.0

Run the code by typing –

mvnw spring-boot:run

Maven

You can secure spring boot by simply including this dependency –

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

You can then get the password when you startup spring boot, but that is not very practical for most usages.

SecurityConfig

Ive moved the Spring Data REST API URL to /rest in application.properties –

spring.data.rest.basePath=/rest

SecurityConfig has –

  • Two users – user(Role – USER), admin(Role – admin)
  • Restrictions –
    • Only “ADMIN” or “USER” roles can access “/rest”,
    • Only “ADMIN” users can POST to the web service –
package com.javabullets.springdata.jparest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.http.HttpMethod;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.
            inMemoryAuthentication()
                .withUser("user").password("user").roles("USER").and()
                .withUser("admin").password("admin").roles("USER","ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
            .antMatchers(HttpMethod.POST, "/rest/parkrunCourses/**").hasRole("ADMIN")
            .antMatchers("/rest/**").hasAnyRole("ADMIN","USER").and()
          .httpBasic()
            .and()
           .csrf().disable();
    }
}

Putting It Together

mvnw spring-boot:run

Ive switched to curl for calling the API’s as its clearer for tutorials –

No credentials – Doesnt Authenticate

curl  -X GET -H "Content-Type:application/json" http://localhost:8080/rest/parkrunCourses/1
{"timestamp":1496069649024,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/rest/parkrunCourses/1"}

user/user credentials – Authenticates

curl -u user:user -X GET -H "Content-Type:application/json" http://localhost:8080/rest/parkrunCourses/1
{
  "courseName" : "Inverness",
  "url" : "http://www.parkrun.org.uk/inverness/",
  "averageTime" : 1582,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/rest/parkrunCourses/1"
    },
    "parkrunCourse" : {
      "href" : "http://localhost:8080/rest/parkrunCourses/1"
    }
  }
}

POST methods – Access Forbidden

curl -u user:user -X POST -H "Content-Type:application/json" -d "{  \"courseName\" : \"adminOnly\",  \"url\" : \"url\",  \"averageTime\" : \"10000\" }" http://localhost:8080/rest/parkrunCourses
{"timestamp":1496069812087,"status":403,"error":"Forbidden","message":"Access is denied","path":"/rest/parkrunCourses"}

POST methods – Access Allowed

curl -u admin:admin -X POST -H "Content-Type:application/json" -d "{  \"courseName\" : \"adminOnly\",  \"url\" : \"url\",  \"averageTime\" : \"10000\" }" http://localhost:8080/rest/parkrunCourses
{
  "courseName" : "adminOnly",
  "url" : "url",
  "averageTime" : 10000,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/rest/parkrunCourses/13"
    },
    "parkrunCourse" : {
      "href" : "http://localhost:8080/rest/parkrunCourses/13"
    }
  }
}

Conclusions

This post shows how Spring Security and Spring Data REST can be combined to secure REST API URL’s and HTTP methods. It used a basic form of Spring authentication, combining a MemoryRealm with the security configuration. We have also demonstrated how to restrict access to REST methods based on user group.

Introduction to Spring Data REST

This tutorial on Spring Data REST shows how Spring Data repositories can be exposed as a REST API. Its a really interesting idea, and can save you a lot of boilerplate code building microservices.

Introduction to Spring Data REST

Spring Boot makes it easy to create a Spring Data REST starter project using Spring Initializr

spring initializr

Spring Initializr – Spring Boot JPA Microservice

Im using Spring Boot 2.0.0, and the HAL Browser to quickly demonstrate REST API.

Download, expand and import into your IDE –

Expanded Spring Boot Project

Expanded Spring Boot Project

Code

For a quickstart the code is available on github at – https://github.com/farrelmr/introtospringdatarest/tree/1.0.0

Let’s keep this simple and use the classes from my parkrunpb project.

The starting point is the 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>

    <groupId>com.javabullets.springdata</groupId>
    <artifactId>jparest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>jparest</name>
    <description>Spring Data Rest Example</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.BUILD-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-rest-hal-browser</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

So we have our JPA object –

package com.javabullets.springdata.jparest;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class ParkrunCourse {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;

private String courseName;
private String url;
private Long averageTime;

public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Long getAverageTime() {
return averageTime;
}
public void setAverageTime(Long averageTime) {
this.averageTime = averageTime;
}
}

Im using JPARepository –

package com.javabullets.springdata.jparest;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

public interface ParkrunCourseRepository extends JpaRepository<ParkrunCourse, Long> {
}

And a script to ensure the data is preloaded – stored in (src\main\resources\import.sql) –

INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (1, 'Inverness', 'http://www.parkrun.org.uk/inverness/', 1582);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (2, 'Aberdeen',    'http://www.parkrun.org.uk/aberdeen/', 1586);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (3, 'Dundee(Camperdown)', 'http://www.parkrun.org.uk/camperdown/', 1752);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (4, 'St Andrews', 'http://www.parkrun.org.uk/standrews/', 1669);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (5, 'Perth', 'http://www.parkrun.org.uk/perth/', 1620);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (6, 'Edinburgh', 'http://www.parkrun.org.uk/edinburgh/', 1523);

Running the Code

Run the code using the maven wrapper – mvnw –

mvnw spring-boot:run

We can then access the HALBrowser on –

http://localhost:8080/browser/index.html#http://localhost:8080/parkrunCourses

Spring Data REST - HALBrowser

Spring Data REST – HALBrowser

The response body returns all the parkrunCourse. You can also do GET, POST, PUT, PATCH and DELETE.

A good trick is to use the browser integrated SQL editor for the H2 database. I covered this in my spring boot security tutorial.

hal+json media type

Spring Data JPA allows you to make calls as json or hal+json. The purpose of hal+json makes it easier to navigate API’s following links. The HALBrowser is great for quickly checking your API.

Conclusions

This example shows how a spring data repository can easily be exposed as a REST API. My next post will look at the practicality of this approach, and what restrictions you might want to apply to your API.

What does Spring DelegatingFilterProxy do?

What does Spring DelegatingFilterProxy do? I had never given it much thought about how Spring Security integrates with web.xml until I had to diagnose an issue involving the DelegatingFilterProxy and my Spring Security configuration.

Isle Of Harris

What does Spring DelegatingFilterProxy do?

I knew the starting point is the springSecurityFilterChain which uses the DelegatingFilterProxy, and this would instantiate the Spring Security filters according to my spring configuration –

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
    </filter-mapping

But what next?

DelegatingFilterProxy

A look at the javadoc for DelegatingFilterProxy states that –

Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that implements the Filter interface

It further states that the filter-name corresponds to a bean in the Spring application context.

So in terms of Spring Security the DelegatingFilterProxy will look though the Spring Application Context for a bean “springSecurityFilterChain”. The only requirement for Delegated beans is that they must implement javax.servlet.Filter.

Initializing the springSecurityFilterChain

The springSecurityFilterChain is initialised in your spring configuration by, which will be passed to your DispatcherServlet –

<http ...> </http>

We can see this in action when we include code to add remove filters in Spring Security –

<http ...>
    ...        
    <custom-filter ref="mySecurityFilter" after="FORM_LOGIN_FILTER"  />
    ...
</http>

You can also see a list of filters created when you up spring security logging

What about Spring Boot?

Spring Security configuration for Spring Boot is simply a matter of adding a reference to “spring-boot-starter-security” to gradle or maven. We can then fine tune the security configurations through @EnableWebSecurity and overriding the configure method of a class extending WebSecurityConfigurerAdapter.

If we dig around under the hood we find that the DelegatingFilterProxy is still used. With the “springSecurityFilterChain” instantiated in SecurityFilterAutoConfiguration, which populates the DelegatingFilterProxyRegistrationBean with “springSecurityFilterChain” (AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) –

@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
    DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
    registration.setOrder(securityProperties.getFilterOrder());
    registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
    return registration;
}

Reference

http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#delegating-filter-proxy

https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/

http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#delegating-filter-proxy

Keeping Documentation Up-To-Date

I returned to a project wiki on confluence after a few months break, and was looking through the documentation wondering if it was still relevent. The code and requirements had changed in places, so I knew some was out of date – however the documentation of the new areas looked good. There were also other design artifacts that seemed important at the time, but now I felt had questionable value.

Problem

In my experience the problem is that we create lots documentation at the start of a project to set direction, but as the project continues it becomes easier to see how the requirements slot into the overall design. This means we are less likely to update the documentation in these areas as they are covered at a high level in the existing documentation. This means the information can slip through the cracks.

Solutions?

I’ve had a look into solutions to this problem –

  • Aims – What do you need to document? From a developer perspective you likely need –
    • Onboarding
    • UML artifacts – Class Diagrams, Sequence Diagrams
    • Server setups might also be relevent
  • Pruning – At the end of a project, or phase, have a task to prune the information. The degree to which you “prune” the information depends on your project. If you are using a wiki you could reference the access log to see when the pages were last used, and there are some tools that can auto-archive but I have not used these.
  • Autogenerate content – This is especially important for documenting software – but look to use tools to generate your design documentation
  • Fresh Eyes – We only see the true value of documentation when we onboard someone new, or return to a project after a break. Then we get to see the cracks in the documentation, and what is not of value

Other?

I would be interested to read how other people manage project documentation, especially in small teams

Java8 – Methods in Interfaces

My previous post on streams demonstrates how useful this feature is to Java8. However it created a problem for the API designers. The problem was how to we extend the existing Collection’s API without breaking existing Collections implementations(guava, apache).

The solution was to allow methods in interfaces, meaning that any implementations already carry an implementation of the extension.

A good example is –

public interface Collection extends Iterable {
    // ....
    default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    // ....
}

Rules on default methods

  • Always public
  • default – keyword

Multiple Inheritance

Java always had multiple inheritance in interfaces, but it wasn’t an issue as it didn’t matter which version of the inherited method was implemented.

interface Fred {
    void run();
}
public class OldSkoolMultipleInheritance implements Runnable, Fred {
    @Override
    public void run() {
        System.out.println("Runnable and Fred share the method signature run()");
    }
}

It’s more complex when default methods are involved, and java 8 has an order of precedence –

  • classes > interfaces
  • children > parent
  • else select or override implementation

Examples

interface Interface1 {
    default void defaultMethod() {
        System.out.println("print me!");
    }
}
interface Interface2 {
    default void defaultMethod() {
        System.out.println("no print me!");
    }
}
  • Classes > Interfaces –
public class DefaultMethodMultipleInheritance implements Interface1, Interface2 {
    @Override
    public void defaultMethod() {
        System.out.println("I win");
    }
}
  • Child Interfaces > Parent Interfaces –
interface Interface3 extends Interface2 {
    default void defaultMethod() {
        System.out.println("No no print me!!!");
    }
}
public class DefaultMethodMultipleInheritance implements Interface2, Interface3 {
    public static void main(String[] args) {
        new DefaultMethodMultipleInheritance().defaultMethod();
    }
}
Output - No no print me!!!
  • Re-implement method in the DefaultMethodsClass and call the specific implementation using super –
Interface1.super.newWave(); or Interface2.super.newWave();

eg –

public class DefaultMethodMultipleInheritance implements Interface1, Interface2 {
    @Override
    public void defaultMethod() {
        Interface1.super.defaultMethod();
    }
}

static methods

The reasoning here is to keep static methods specific to your interface in one location –

  • public
  • static
  • Never inherited

Java8 – Date and Time examples

This post continues my look at Java8 features with the date API, including examples.

Before Java8 we had two date implementations – java.util.Date and java.util.Calendar. These implementations had numerous issues which Java8 sought to resolve. This article considers these issues, as well as providing a examples using the java.time API.

Issues

  • API Design – java.util.Date and java.util.Calendar both had issues like months starting from 0, or in the case of Date year starting from 1900. Date also represented a point in time, seconds from the epoch, while its toString method included a TimeZone. There was also no single class representing Time or Date
  • Thread-safety – DateFormat not thread safe

De-facto standards – Joda-Time evolved as a defacto standard for Java dates. It is to Java8’s credit that they developed the Java8 JSR-310 API’s with the main joda-time developer Stephen Colebourne.

New Features

  • New package – java.time.*
  • Core Classes – LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Period, Duration, Instant
  • Immutable
  • Factory methods

Examples

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

public class DateTimeExamples {

	public static void main(String[] args) {
		preJava8DateAndCalendar();
		java8LocalDateAndLocalTimeExamples();
		java8LocalDateTimeExamples();
		java8ZonedDateTime();
		java8CustomAdjuster();
		java8PeriodOrDuration();
	}

	public static void preJava8DateAndCalendar() {
		System.out.println("\npreJava8DateAndCalendar\n");

		Date today = new Date();
		System.out.println("Note the time includes the default timezone - " + today.toString());

		Date twentySevenFeb2017Date = new Date(117, 1, 27);
		System.out.println("Now deprecated new Date(day, month, year) - but note month starts at zero, and year 1900 - " + twentySevenFeb2017Date);

		Calendar twentySevenFeb2017Calendar = new GregorianCalendar(2017,1,27);
		System.out.println("Calendar - month starts at zero, and but year fixed - " + twentySevenFeb2017Calendar.getTime());

		DateFormat ddMMyyySDF = new SimpleDateFormat("dd/MM/yyyy");
		System.out.println("DateFormat not ThreadSafe - " + ddMMyyySDF.format(twentySevenFeb2017Date));
	}

	public static void java8LocalDateAndLocalTimeExamples() {
		// Date and Time split up - LocalDate, LocalTime
		// Called through factory methods of, now, parse
		// Immutable
		// No TimeZone
		System.out.println("\njava8LocalDateAndLocalTimeExamples\n");

		LocalDate currentLocalDate = LocalDate.now();
		System.out.println("currentLocalDate - yyyy-MM-dd - " + currentLocalDate);

		// Month now not based on 0, and year not based on 1900
		LocalDate twentySevenFeb2017LocalDate = LocalDate.of(2017, 2, 27);
		System.out.println("twentySevenFeb2017LocalDate - yyyy-MM-dd - " + twentySevenFeb2017LocalDate);

		twentySevenFeb2017LocalDate = twentySevenFeb2017LocalDate.withYear(2017).withMonth(12).withDayOfMonth(25);
		System.out.println("twentySevenFeb2017LocalDate - with -  " + twentySevenFeb2017LocalDate);

		LocalDate parseTwentySevenFeb2017LocalDate = LocalDate.parse("2017-02-27", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
		System.out.println("parseTwentySevenFeb2017LocalDate - pattern - yyyy-MM-dd - " + parseTwentySevenFeb2017LocalDate);

		// increment using plus, decrement using minus
		twentySevenFeb2017LocalDate = twentySevenFeb2017LocalDate.plusDays(1);
		System.out.println("twentySevenFeb2017LocalDate - immutable - " + twentySevenFeb2017LocalDate);

		// Time with no date
		LocalTime currentLocalTime = LocalTime.now();
		System.out.println("currentLocalTime - yyyy-MM-dd - " + currentLocalTime);

		LocalTime parseLocalTime = LocalTime.parse("13:44");
		System.out.println("parseLocalTime - " + parseLocalTime);

		parseLocalTime = LocalTime.parse("13:44:25");
		System.out.println("parseLocalTime - immutable - " + parseLocalTime);

	}

	public static void java8LocalDateTimeExamples() {
		// Combines LocalDate and LocalTime
		// Called through factory methods of, now, parse
		// Immutable
		// No TimeZone
		System.out.println("\njava8LocalDateTimeExamples\n");

		LocalDateTime currentLocalDateTime = LocalDateTime.now();
		System.out.println("currentLocalDateTime " + currentLocalDateTime);		

		currentLocalDateTime = LocalDateTime.parse("2019-06-21T23:53:00.123");
		System.out.println("currentLocalDateTime - parse -  " + currentLocalDateTime);

		currentLocalDateTime = currentLocalDateTime.minusYears(10);
		System.out.println("currentLocalDateTime - minus 10 years -  " + currentLocalDateTime);
	}

	public static void java8ZonedDateTime() {
		System.out.println("\njava8ZonedDateTime\n");

		// Associate DateTime with TimeZone
		ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.now(), ZoneId.systemDefault());
		System.out.println("zonedDateTime - " + zonedDateTime);

		ZoneId australiaSydneyZoneId = ZoneId.of("Australia/Sydney");
		ZonedDateTime australiaSydneyZonedDateTime = ZonedDateTime.of(LocalDateTime.now(), australiaSydneyZoneId);
		System.out.println("australiaSydneyZonedDateTime - " + australiaSydneyZonedDateTime);
	}	

	public static void java8CustomAdjuster() {
		System.out.println("\njava8CustomAdjuster\n");
		TemporalAdjuster dueDateAdjuster = TemporalAdjusters.ofDateAdjuster((LocalDate localDate) -&amp;gt; localDate.plusWeeks(40));

		LocalDate startLocalDate = LocalDate.now();
		System.out.println("Due Date - " + startLocalDate.with(dueDateAdjuster));
	}

	public static void java8PeriodOrDuration() {
		System.out.println("\njava8DurationPeriodAndInstance\n");
		// Period - Duration in day, weeks, month, years
		Period examplePeriod = Period.of(72,6,10);
		System.out.println("examplePeriod " + examplePeriod);

		LocalDate localDatePlusExamplePeriod = LocalDate.now().plus(examplePeriod);
		System.out.println("localDatePlusExamplePeriod " + localDatePlusExamplePeriod);

		// Duration - days, hours, minutes, seconds
		Duration exampleDuration = Duration.ofHours(5);
		System.out.println("exampleDuration " + exampleDuration);

		LocalTime exampleDurationLocalTime = LocalTime.now().plus(exampleDuration);
		System.out.println("exampleDurationLocalTime " + exampleDurationLocalTime);		

	}	

}

Java8 – Optional’s Overview with Examples

We can limit the opportunities for NullPointerException’s by following good practice like –

  • Don’t return null’s from methods
  • Don’t pass null’s as arguments

Another tool is to use java.lang.Optional. Java8 optional formalises the approach used in other languages, and already existed in some Java libraries. Optional is simply an object wrapper. The key to its use is to use the methods in the Optional classes.

Examples

Im following a similar style to my last post Java8 – Streams Cookbook and have included a class with a number of examples using Optional

import java.util.Optional;
public class OptionalExamples {
	// Populate Bike with Optional<Wheels>
	private static Bike colnagoBike = new Bike(Optional.of(new Wheels("mavic", 32)), "colnago");
	// Dont do this - use a Optional.ofNullable
	private static Bike nullBike = new Bike(null, "nowheels");
	// Use a Optional.ofNullable
	private static Bike ofNullableBike = new Bike(Optional.ofNullable(null), "nowheels");
	public static void main(String[] args) {
		emptyOptionals();
		nullObjectsInOptionals();
		workingWithValuesInOptionals();
	}
	private static void workingWithValuesInOptionals() {
		// Working with Values
		// Populated Optional - now begin populating with Object
		Optional<Bike> optionalColnagoBike = Optional.of(colnagoBike);
		if (optionalColnagoBike.isPresent()) {
			System.out.println("isPresent() - ok - but not much improvement on != null");
		}
		optionalColnagoBike.ifPresent(bike -> System.out.println("ifPresent(Consumer) returns " + bike.getBrand()));
		// Filtering and mapping in combination with Lambdas
		Bike orElseThrowBike = optionalColnagoBike.filter(b -> "colnago".equals(b.getBrand())).get();
		//
		Optional<String> brand = optionalColnagoBike.map(b -> b.getBrand());
		brand.ifPresent(b -> System.out.println("brand " + b));
		// flatMap to prevent Optional<Optional<Wheels>>
		Optional<Wheels> wheels = optionalColnagoBike.flatMap(b -> b.getWheels());
		wheels.ifPresent(w -> System.out.println("flatMap - Wheel Brand " + w.getBrand()));
	}
	private static void nullObjectsInOptionals() {
		// of - Populate with null object - Throws Exception
		try {
			Optional<Bike> optionalNullBike = Optional.of(null);
		} catch (java.lang.NullPointerException nfe) {
			System.out.println("Cant call Optional.of(null) - " + nfe.getMessage());
		}
		// ofNullable - allows null
		System.out.println("We can pass a null with ofNullable");
		Optional<Bike> optionalOfNullableBike = Optional.ofNullable(null);
		System.out.println("optionalOfNullableBike.isPresent() returns " + optionalOfNullableBike.isPresent());
	}
	private static void emptyOptionals() {
		// Empty Optional - empty container with no object
		Optional<Bike> optionalEmptyBike = Optional.empty();
		// call get() on empty object throws NoSuchElementException
		try {
			Bike emptyBike = optionalEmptyBike.get();
		} catch (java.util.NoSuchElementException e) {
			System.out.println("get() on empty Optional throws java.util.NoSuchElementException " + e.getMessage());
		}
		// isPresent - check if object is empty - but not much advantage over !=
		// null checks
		if (!optionalEmptyBike.isPresent()) {
			System.out.println("isPresent() - ok - but not much improvement on != null");
		}
		// Better Alternatives -
		// orElse - returns a default object if none set
		Bike orElseBike = optionalEmptyBike.orElse(colnagoBike);
		System.out.println("orElse - Optional is empty so return colnagoBike " + orElseBike.getBrand());
		// ifPresent(Consumer<? extends Bike>) - this prints nothing as Optional
		// is empty
		optionalEmptyBike.ifPresent(bike -> System.out.println("ifPresent(Consumer) returns " + bike.getBrand()));
		// orElseThrow - Throw Exception
		try {
			Bike orElseThrowBike = optionalEmptyBike.orElseThrow(NoBikeException::new);
		} catch (NoBikeException nbe) {
			System.out.println("orElseThrow NoBikeException");
		}
	}
}
class Bike {
	private Optional<Wheels> wheels;
	private String brand;
	public Bike(Optional<Wheels> wheels, String brand) {
		this.wheels = wheels;
		this.brand = brand;
	}
	public Optional<Wheels> getWheels() {
		return wheels;
	}
	public void setWheels(Optional<Wheels> wheels) {
		this.wheels = wheels;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
}
class Wheels {
	private String brand;
	private int spokes;
	public Wheels(String brand, int spokes) {
		this.brand = brand;
		this.spokes = spokes;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public int getSpokes() {
		return spokes;
	}
	public void setSpokes(int spokes) {
		this.spokes = spokes;
	}
}
class NoBikeException extends Exception {
	private static final long serialVersionUID = 1L;
}
 

 

 

1 2 3 12