Tuesday, August 23, 2016

Spring Security OAuth2 with Google

I needed to create a web app using Spring MVC and secure it using OAuth2 with Google as a provider for authentication. Saket's Blog (posted back in September 2014) provided a good guide. But I needed something slightly different. I needed one that uses Maven (not Gradle) and minus Spring Boot. So, I thought it would just be a simple thing to do. But as I found out, it was not so simple, and I'm writing some details here to help others in using OAuth2 to secure their Spring MVC web apps.

Here's what I configured to make my web application use OAuth2 with Google as the provider.

  • Enable Spring Security with @EnableWebSecurity.
  • Add an OAuth2ClientAuthenticationProcessingFilter bean to the security filter chain just before the filter security interceptor. This authentication processing filter is configured to know where the authorization code resource can be found. This makes it possible for it to throw an exception that redirects the user to the authorization server for authentication and authorization.
  • Set an authentication entry point (specifically a LoginUrlAuthenticationEntryPoint) that redirects to the same URL as the one being detected by the OAuth2ClientAuthenticationProcessingFilter. Say, we choose the path “/oauth2/callback”. This path should be the one used by both authentication entry point and authentication processing filter.
  • Add @EnableOAuth2Client to create an OAuth2ClientContextFilter bean and make an OAuth2ClientContext available in request scope. To make request scope possible in the security filter chain, add a RequestContextListener or RequestContextFilter.
  • Add the OAuth2ClientContextFilter bean to the security filter chain just after the exception translation filter. This filter handles the exception that redirects the user (thrown by the authentication process filter). It handles this exception by sending a redirect.

Authorization Code Resource

The authentication processing filter needs to know where to redirect the user for authentication. So, a bean is configured and injected into the authentication process filter.

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
@PropertySource("classpath:google-oauth2.properties")
public class ... extends WebSecurityConfigurerAdapter {
  ...
  @Value("${oauth2.clientId}")
  private String clientId;
  @Value("${oauth2.clientSecret}")
  private String clientSecret;
  @Value("${oauth2.userAuthorizationUri}")
  private String userAuthorizationUri;
  @Value("${oauth2.accessTokenUri}")
  private String accessTokenUri;
  @Value("${oauth2.tokenName}")
  private String tokenName;
  @Value("${oauth2.scope}")
  private String scope;
  @Value("${oauth2.userInfoUri}")
  private String userInfoUri;

  @Value("${oauth2.filterCallbackPath}")
  private String oauth2FilterCallbackPath;

  @Bean
  @Description("Authorization code resource")
  public OAuth2ProtectedResourceDetails authorizationCodeResource() {
    AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
    ...
    details.setClientId(clientId);
    details.setClientSecret(clientSecret);
    details.setUserAuthorizationUri(userAuthorizationUri);
    details.setAccessTokenUri(accessTokenUri);
    details.setTokenName(tokenName);
    String commaSeparatedScopes = scope;
    details.setScope(parseScopes(commaSeparatedScopes));
    details.setAuthenticationScheme(AuthenticationScheme.query);
    details.setClientAuthenticationScheme(AuthenticationScheme.form);
    return details;
  }

  private List<String> parseScopes(String commaSeparatedScopes) {...}

  ...

  @Bean
  @Description("Enables ${...} expressions in the @Value annotations"
      + " on fields of this configuration. Not needed if one is"
      + " already available.")
  public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
  }
}

Note that the authorization code resource details are externalized. These details include the URI for authentication, the URI to exchange an authorization code with an access token, client ID, and client secret.

Authentication Processing Filter

With an authorization code resource bean configured, we configure an authentication processing filter bean that will redirect to the authorization code resource when the incoming request is not yet authenticated. Note that the authentication processing filter is injected with an OAuth2RestTemplate that points to the authorization code resource.

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
@PropertySource("classpath:google-oauth2.properties")
public class ... extends WebSecurityConfigurerAdapter {
  @Autowired
  private OAuth2ClientContext oauth2ClientContext;
  ...
  @Bean
  @Description("Authorization code resource")
  public OAuth2ProtectedResourceDetails authorizationCodeResource() {
    AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
    ...
    return details;
  }

  @Bean
  @Description("Filter that checks for authorization code, "
      + "and if there's none, acquires it from authorization server")
  public OAuth2ClientAuthenticationProcessingFilter
        oauth2ClientAuthenticationProcessingFilter() {
    // Used to obtain access token from authorization server (AS)
    OAuth2RestOperations restTemplate = new OAuth2RestTemplate(
        authorizationCodeResource(),
        oauth2ClientContext);
    OAuth2ClientAuthenticationProcessingFilter filter =
        new OAuth2ClientAuthenticationProcessingFilter(oauth2FilterCallbackPath);
    filter.setRestTemplate(restTemplate);
    // Set a service that validates an OAuth2 access token
    // We can use either Google API's UserInfo or TokenInfo
    // For this, we chose to use UserInfo service
    filter.setTokenServices(googleUserInfoTokenServices());
    return filter;
  }

  @Bean
  @Description("Google API UserInfo resource server")
  public GoogleUserInfoTokenServices googleUserInfoTokenServices() {
    GoogleUserInfoTokenServices userInfoTokenServices =
        new GoogleUserInfoTokenServices(userInfoUri, clientId);
    return userInfoTokenServices;
  }
  ...
}

Note that the access token is further checked by using it to access a secured resource (provided by a resource server). In this case, the Google API to retrieve user information like email and photo is used.

Arguably, the authorization code resource does not need to be configured as a bean, since it is only used by the authentication processing filter.

Authentication Entry Point

The authentication processing filter and the authentication entry point are configured to detect the same request path.

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
@PropertySource("classpath:google-oauth2.properties")
public class ... extends WebSecurityConfigurerAdapter {
  ...
  public OAuth2ProtectedResourceDetails authorizationCodeResource() {...}

  @Bean
  @Description("Filter that checks for authorization code, "
      + "and if there's none, acquires it from authorization server")
  public OAuth2ClientAuthenticationProcessingFilter
        oauth2ClientAuthenticationProcessingFilter() {
    ...
    OAuth2ClientAuthenticationProcessingFilter filter =
        new OAuth2ClientAuthenticationProcessingFilter(oauth2FilterCallbackPath);
    ...
    return filter;
  }
  ...
  @Bean
  public AuthenticationEntryPoint authenticationEntryPoint() {
    return new LoginUrlAuthenticationEntryPoint(oauth2FilterCallbackPath);
  }
  ...
}

So, how does this all work?

This is how the security filter chain will look like with the added custom filters. Note that for brevity, not all filters were included.

Web Browser
↓
SecurityContextPersistenceFilter
↓
LogoutFilter
↓
ExceptionTranslationFilter
↓
OAuth2ClientContextFilter
↓
OAuth2ClientAuthenticationProcessingFilter
↓
FilterSecurityInterceptor
↓
Secured Resource

So, here's what happens at runtime. The client referred to here, is a web application that uses OAuth2 for authentication.

  1. Request for a secured resource on the client is received. It travels through the security filter chain until FilterSecurityInterceptor. The request has not been authenticated yet (i.e. security context does not contain an authentication object), and the FilterSecurityInterceptor throws an exception (AuthenticationCredentialsNotFoundException). This authentication exception travels up the security filter chain, and is handled by ExceptionTranslationFilter. It detects that an authentication exception occured, and delegates to the authentication entry point. The configured authentication entry point (LoginUrlAuthenticationEntryPoint) redirects the user to a new location (e.g. “/oauth2/callback”). The request for a secured resource is saved, and request processing completes.
    Web Browser
    ↓
    SecurityContextPersistenceFilter
    ↓
    LogoutFilter
    ↓
    ExceptionTranslationFilter Delegate to authentication entry point
    ↓ ↑
    OAuth2ClientContextFilter
    ↓ ↑
    OAuth2ClientAuthenticationProcessingFilter
    ↓ ↑
    FilterSecurityInterceptor Throws exception!
     
    Secured Resource
  2. Since a redirect is the response of the previous request, a request to the new location is made. This request travels through the security filter chain until OAuth2ClientAuthenticationProcessingFilter determines that it is a request for authentication (e.g. it matches “/oauth2/callback”). Upon checking the request, it determines that there’s no authorization code, and throws an exception (UserRedirectRequiredException) that contains a URL to the authorization code resource (e.g. https://accounts.google.com/o/oauth2/v2/auth?client_id=https://accounts.google.com/o/oauth2/v2/auth?client_id=…&redirect_uri=http://…/…/oauth2/callback&response_type=code&scope=…&state=…). This exception is handled by OAuth2ClientContextFilter. And request processing completes.
    Web Browser
    ↓
    SecurityContextPersistenceFilter
    ↓
    LogoutFilter
    ↓
    ExceptionTranslationFilter
    ↓
    OAuth2ClientContextFilter Handle exception by sending redirect
    ↓ ↑
    OAuth2ClientAuthenticationProcessingFilter Throws exception!
     
    FilterSecurityInterceptor
     
    Secured Resource
  3. Just as before, the redirect is followed. This time, it is a redirect to the authorization server (e.g. https://accounts.google.com/o/oauth2/v2/auth). The user is asked to authenticate (if not yet authenticated).
  4. Next, the user is asked to allow/authorize the client to have access to his/her information. After the user decides to allow/authorize the client, the authorization server redirects back to the client (based on the redirect_uri parameter).
  5. Request on the client is received. It travels through the security filter chain until OAuth2ClientAuthenticationProcessingFilter determines that it is a request for authentication (e.g. it matches “/oauth2/callback”). It finds that the request contains an authorization code, and proceeds to exchange the authorization code for an access token. Furthermore, it validates the access token by accessing a resource (on a resource server), and creates an Authentication object (with Principal and GrantedAuthority objects). This will be stored in the session and in the security context. And request processing completes with a redirect to the saved request (from #1).
    Web Browser
    ↓
    SecurityContextPersistenceFilter
    ↓
    LogoutFilter
    ↓
    ExceptionTranslationFilter
    ↓
    OAuth2ClientContextFilter
    ↓
    OAuth2ClientAuthenticationProcessingFilter Exchanges authorization code with access token; creates authentication object and stores it in session
     
    FilterSecurityInterceptor
     
    Secured Resource
  6. Just as before, the redirect is followed. It travels through the security filter chain. This time, the FilterSecurityInterceptor allows the request to proceed, since there is an authentication object in the security context (retrieved from session). The secured resource is provided to the user (e.g. render a view/page of the secured resource).
    Web Browser
    ↓
    SecurityContextPersistenceFilter
    ↓
    LogoutFilter
    ↓
    ExceptionTranslationFilter
    ↓
    OAuth2ClientContextFilter
    ↓
    OAuth2ClientAuthenticationProcessingFilter
    ↓
    FilterSecurityInterceptor
    ↓
    Secured Resource :)

Code and Credits

The code for the sample web application can be found here at my GitHub account.

Again, thanks to Saket's Blog.

Also, check out Security Architecture with Spring at Java Code Geeks.

Thursday, July 21, 2016

One-shot Delete with Hibernate (JPA)

In older versions of Hibernate, I can see the one-shot delete indicated in the manual. But newer versions no longer have this section. I'm not sure why. So, in this post, I take a look if it still works.

The one-shot delete section says:

Deleting collection elements one by one can sometimes be extremely inefficient. Hibernate knows not to do that in the case of an newly-empty collection (if you called list.clear(), for example). In this case, Hibernate will issue a single DELETE.

Suppose you added a single element to a collection of size twenty and then remove two elements. Hibernate will issue one INSERT statement and two DELETE statements, unless the collection is a bag. This is certainly desirable.

However, suppose that we remove eighteen elements, leaving two and then add thee new elements. There are two possible ways to proceed

  • delete eighteen rows one by one and then insert three rows
  • remove the whole collection in one SQL DELETE and insert all five current elements one by one

Hibernate cannot know that the second option is probably quicker. It would probably be undesirable for Hibernate to be that intuitive as such behavior might confuse database triggers, etc.

Fortunately, you can force this behavior (i.e. the second strategy) at any time by discarding (i.e. dereferencing) the original collection and returning a newly instantiated collection with all the current elements.

One-shot-delete does not apply to collections mapped inverse="true".

The inverse="true" is for (Hibernate Mapping) XML. But in this post, we'll see how "one-shot delete" works in JPA (with Hibernate as the provider).

We will try different approaches and see which one will result to a one-shot delete.

  1. Bi-directional one-to-many
  2. Uni-directional one-to-many (with join table)
  3. Uni-directional one-to-many (with no join table)
  4. Uni-directional one-to-many (using ElementCollection)

We'll use a Cart entity with many CartItems.

Bi-directional One-to-Many

For this, we have references from both sides.

@Entity
public class Cart { ...
 @OneToMany(mappedBy="cart", cascade=ALL, orphanRemoval=true)
 Collection<OrderItem> items;
}

@Entity
public class CartItem { ...
 @ManyToOne Cart cart;
}

To test this, we insert one row to the table for Cart, and three or more rows to the table for CartItem. Then, we run the test.

public class CartTests { ...
 @Test
 public void testOneShotDelete() throws Exception {
  Cart cart = entityManager.find(Cart.class, 53L);
  for (CartItem item : cart.items) {
   item.cart = null; // remove reference to cart
  }
  cart.items.clear(); // as indicated in Hibernate manual
  entityManager.flush(); // just so SQL commands can be seen
 }
}

The SQL commands shown had each item deleted individually (and not as a one-shot delete).

delete from CartItem where id=?
delete from CartItem where id=?
delete from CartItem where id=?

Discarding the original collection did not work either. It even caused an exception.

public class CartTests { ...
 @Test
 public void testOneShotDelete() throws Exception {
  Cart cart = entityManager.find(Cart.class, 53L);
  // remove reference to cart
  cart.items = new LinkedList<CartItem>(); // discard, and use new collection
  entityManager.flush(); // just so SQL commands can be seen
 }
}
javax.persistence.PersistenceException:
    org.hibernate.HibernateException:
        A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: ….Cart.items

I tested this with Hibernate 4.3.11 and HSQL 2.3.2. If your results vary, please hit the comments.

Uni-directional One-to-Many (With Join Table)

For this, we make changes to the mapping. This causes a join table to be created.

@Entity
public class Cart { ...
 @OneToMany(cascade=ALL)
 Collection<OrderItem> items;
}

@Entity
public class CartItem { ...
 // no @ManyToOne Cart cart;
}

Again, we insert one row to the table for Cart, and three or more rows to the table for CartItem. We also have to insert appropriate records to the join table (Cart_CartItem). Then, we run the test.

public class CartTests { ...
 @Test
 public void testOneShotDelete() throws Exception {
  Cart cart = entityManager.find(Cart.class, 53L);
  cart.items.clear(); // as indicated in Hibernate manual
  entityManager.flush(); // just so SQL commands can be seen
 }
}

The SQL commands shown had the associated rows in the join table deleted (with one command). But the rows in the table for CartItem still exist (and did not get deleted).

delete from Cart_CartItem where cart_id=?
// no delete commands for CartItem

Hmmm, not exactly what we want, since the rows in the table for CartItem still exist.

Uni-directional One-to-Many (No Join Table)

Starting with JPA 2.0, the join table can be avoided in a uni-directional one-to-many by specifying a @JoinColumn.

@Entity
public class Cart { ...
 @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
 @JoinColumn(name="cart_id", updatable=false, nullable=false)
 Collection<OrderItem> items;
}

@Entity
public class CartItem { ...
 // no @ManyToOne Cart cart;
}

Again, we insert one row to the table for Cart, and three or more rows to the table for CartItem. Then, we run the test.

public class CartTests { ...
 @Test
 public void testOneShotDelete() throws Exception {
  Cart cart = entityManager.find(Cart.class, 53L);
  cart.items.clear(); // as indicated in Hibernate manual
  entityManager.flush(); // just so SQL commands can be seen
 }
}

Discarding the original collection also did not work either. It also caused the same exception (as with bi-directional one-to-many).

javax.persistence.PersistenceException:
    org.hibernate.HibernateException:
        A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: ….Cart.items

Uni-directional One-to-Many (with ElementCollection)

JPA 2.0 introduced @ElementCollection. This allows one-to-many relationships to be established with the many-side being either @Basic or @Embeddable (i.e. not an @Entity).

@Entity
public class Cart { ...
 @ElementCollection // @OneToMany for basic and embeddables
 @CollectionTable(name="CartItem") // defaults to "Cart_items" if not overridden
 Collection<OrderItem> items;
}

@Embeddable // not an entity!
public class CartItem {
 // no @Id
 // no @ManyToOne Cart cart;
 private String data; // just so that there are columns we can set
}

Again, we insert one row to the table for Cart, and three or more rows to the table for CartItem. Then, we run the test.

public class CartTests { ...
 @Test
 public void testOneShotDelete() throws Exception {
  Cart cart = entityManager.find(Cart.class, 53L);
  cart.items.clear(); // as indicated in Hibernate manual
  entityManager.flush(); // just so SQL commands can be seen
 }
}

Yey! The associated rows for CartItem were deleted in one shot.

delete from CartItem where Cart_id=?

Closing Thoughts

One-shot delete occurs with uni-directional one-to-many using ElementCollection (where the many-side is an embeddabled, and not an entity).

In the uni-directional one-to-many with join table scenario, deleting entries in a join table doesn't add much value.

I'm not sure why one-shot delete works (or why it works this way) in Hibernate. But I do have a guess. And that is the underlying JPA provider could not do a one-shot delete because it could not ensure that the many-side entity is not referenced by other entities. Unlike the ElementCollection, the many-side is not an entity and cannot be referenced by other entities.

Now, this does not mean that you have to use ElementCollection all the time. Perhaps the one-shot delete only applies to aggregate roots. In those cases, using Embeddable and ElementCollection might be appropriate for a collection of value objects that make up an aggregate. When the aggregate root is removed, then it would be good to see that the "child" objects should be removed as well (and in an efficient manner).

I wish there was a way in JPA to indicate that the child entities are privately owned and can be safely removed when the parent entity is removed (e.g. similar to @PrivateOwned in EclipseLink). Let's see if it will be included in a future version of the API.

Hope this helps.

Wednesday, July 20, 2016

Reference by Identity in JPA

In a previous post, I mentioned that I opted to reference other aggregates by their primary key, and not by type. I usually use this approach (a.k.a. disconnected domain model) when working with large or complex domain models. In this post, let me try to explain further how it can be done in JPA. Note that the resulting DDL scripts will not create a foreign key constraint (unlike the one shown in the previous post).

Reference by Identity

In most JPA examples, every entity references another entity, or is being referenced by another entity. This results into an object model that allows traversal from one entity to any other entity. This can cause unwanted traversals (and unwanted cascade of persistence operations). As such, it would be good to prevent this, by referencing other entities by ID (and not by type).

The code below shows how OrderItem references a Product entity by its primary key (and not by type).

@Entity
public class Product {
 @Id private Long id;
 // ...
}

@Entity
public class Order {
 // ...
 @OneToMany(mappedBy="order")
 private Collection<OrderItem> items;
}

@Entity
public class OrderItem {
 // ...
 @ManyToOne
 private Order order;
 // @ManyToOne
 // private Product product;
 private Long productId;
 // ...
}

There are several ways to get the associated Product entities. One way is to use a repository to find products given the IDs (ProductRepository with a findByIdIn(List<Long> ids) method). As mentioned in previous comments, please be careful not to end up with the N+1 selects problem.

Custom identity types can also be used. The example below uses ProductId. It is a value object. And because of JPA, we needed to add a zero-arguments constructor.

@Embeddable
public class ProductId {
 private Long id;
 public ProductId(long id) {
  this.id = id;
 }
 public long getValue() { return id; }
 // equals and hashCode
 protected ProductId() { /* as required by JPA */ }
}

@Entity
public class Product {
 @EmbeddedId private ProductId id;
 // ...
}

@Entity
public class Order { // ...
 @OneToMany(mappedBy="order")
 private Collection<OrderItem> items;
}

@Entity
public class OrderItem {
 // ...
 @ManyToOne
 private Order order;
 // @ManyToOne
 // private Product product;
 @Embedded private ProductId productId;
 // ...
}

But this will not work if you're using generated values for IDs. Fortunately, starting with JPA 2.0, there are some tricks around this, which I'll share in the next section.

Generated IDs

In JPA, when using non-@Basic types as @Id, we can no longer use @GeneratedValue. But using a mix of property and field access, we can still use generated value and ProductId.

@Embeddable
@Access(AccessType.FIELD)
public class ProductId {...}

@Entity
@Access(AccessType.FIELD)
public class Product {
 @Transient private ProductId id;
 public ProductId getId() { return id; }
 // ...
 private Long id_;
 @Id
 @GeneratedValue(strategy=...)
 @Access(AccessType.PROPERTY)
 protected Long getId_() { return id_; }
 protected void setId_(Long id_) {
  this.id_ = id_;
  this.id = new ProductId(this.id_);
 }
}

@Entity
public class Order { // ...
 @OneToMany(mappedBy="order")
 private Collection<OrderItem> items;
}

@Entity
public class OrderItem {
 // ...
 @ManyToOne
 private Order order;
 // @ManyToOne
 // private Product product;
 @Embedded private ProductId productId;
 // ...
}

The trick involves using property access for the generated ID value (while keeping the rest with field access). This causes JPA to use the setter method. And in it, we initialize the ProductId field. Note that the ProductId field is not persisted (marked as @Transient).

Hope this helps.