Skip to main content

How To Add the OPA Spring Boot SDK to an Existing Spring Application

This guide explains how you can add the OPA Spring Boot SDK to an existing Spring application in order to implement HTTP request authorization.

Overview

The OPA Spring Boot SDK wraps the OPA Java SDK with the OPAAuthorizationManager class, which implements Spring's AuthorizationManager interface, allowing it to be utilized with Spring's HttpSecurity. In this guide, you will learn:

  • How to add the OPA Spring Boot SDK as a dependency for your application.
  • How to configure the OPAAuthorizationManager type for your application.
  • How to add the OPAAuthorizationManager to your security configuration.
  • How to inject application-specific information into your OPA requests using the ContextDataProvider interface.

This guide is intended for developers who are already comfortable working in Java with Spring. If you would like to learn more about spring, consider their Getting Started page.

note

Spring's AuthorizationManager type has a type parameter allowing that single interface to be used for both request and method security. At time of writing, OPAAuthorizationManager only implements AuthorizationManager<RequestAuthorizationContext>, meaning it is not able to support method security yet.

tip

The Spring documentation has a section on how to adapt legacy AccessDecisionManager and AccessDecisionVoters which may be of interest if your application still utilizes these types.

Adding the OPA Spring Boot SDK to your Project

Follow the instructions on the Maven Central Repository page for the com.styra.opa/springboot to add the OPA Spring Boot SDK as a dependency.

Here are two examples of using the OPA Spring Boot SDK in Gradle based projects:

Configuring OPAAuthorizationManager

The OPAAuthorizationManager class has a small surface area for configuration. The table below provides an overview.

DescriptionDefault ValueWhy Change This?
OPAClientOPAClient(opa_url), where opa_url defaults to http://localhost:8181, or the value of the OPA_URL environment variable if it is set.You need to customize the OPA URL, or modify the OPAClient's settings directly.
OPA pathnull (default decision)You would like the OPA client to obtain decisions from a non-default rule.
ContextDataProvidernull (no extra data injected)You would like to include extra, application-specific data in the OPA policy's input at input.context.data.
tip

A "default decision" corresponds to the default_decision OPA configuration key, see OPA's Configuration doc for more information.

Configuration is performed at the time that the authorization manager is instantiated via the class constructor. The constructor is overloaded to allow any of these values to be omitted, though they are always ordered (from right to left): OPA client, OPA path, context data provider.

A minimal way to create an OPAAuthorizationManager is:

// ...

import com.styra.opa.springboot.OPAAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;

// ...

AuthorizationManager<RequestAuthorizationContext> opaauthman = new OPAAuthorizationManager();

The authorization manager will be instantiated with the default values discussed in the table above. This is suitable for use cases where:

  • You can easily control the OPA_URL environment variable, or where the default http://localhost:8181 URL is adequate.
  • Your policy's entry point is configured as the default decision for your OPA instance.
  • You don't need to inject any application specific data into policy input.

A maximal setup OPAAuthorizationManager, setting all possible configuration values explicitly:

// ...

import com.styra.opa.OPAClient;
import com.styra.opa.springboot.ContextDataProvider;
import com.styra.opa.springboot.OPAAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;

// ...

// Implement the default OPA client setup explicitly.
String opaURL = "http://localhost:8181";
String opaURLEnv = System.getenv("OPA_URL");
if (opaURLEnv != null) {
opaURL = opaURLEnv;
}
OPAClient opa = new OPAClient(opaURL);

// This corresponds to the rule data.policy.main in the OPA policy bundle.
String policyEntrypoint = "policy/main";

// Assumes you have defined a suitable makeMyCustomProvider() elsewhere.
ContextDataProvider ctxprovider = makeMyCustomProvider();

AuthorizationManager<RequestAuthorizationContext> opaauthman = new OPAAuthorizationManager(opa, policyEntrypoint, ctxprovider);

Adding OPAAuthorizationManager to your Security Configuration

To cause Spring to secure your HTTP APIs using the OPAAuthorizationManager, you must add it to your SecurityFilterChain. Typically, this happens in a method inside of your security configuration class, which should have the @Configuration and @EnableWebSecurity annotations. Inside of the method where your SecurityFilterChain is created, you should have an authorizeHttpRequests call where you can use a request matcher such as .anyRequest() with the .access() method to register the authorization manager.

The following code snippet shows a minimal security configuration that only sets up an OPAAuthorizationManager and applies it to every request.

package com.example;

import com.styra.opa.springboot.OPAAuthorizationManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;


@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

AuthorizationManager<RequestAuthorizationContext> am = new OPAAuthorizationManager();

http.authorizeHttpRequests(authorize -> authorize
.anyRequest()
.access(am)
);

return http.build();
}

}

Here are some examples of adding an OPAAuthorizationManager to a Spring security configuration:

Injecting Application-Specific Data with a ContextDataProvider

The OPA Spring Boot SDK creates a rich input document with most of the information about each HTTP request included, following the AuthZEN format. You can find more details here. However, for some advanced use cases, it may be necessary to include additional data in the policy input. As an escape hatch, the OPAAuthorizationManager class, when instantiated with a ContextDataProvider, can include arbitrary structured data in the policy input at input.context.data.

A ContextDataProvider is a callback method (@FunctionalInterface) which gets access to the same Authentication and RequestAuthorizationContext objects that the check() and verify() methods on the authorization manager see. This can be used to drive custom logic that looks up information based on the authenticated user, the HTTP request, or some other contextual data. When implementing your custom ContextDataProvider implementation, keep in mind that the Object it returns will be serialized to JSON by Jackson. If you do not need to consider per-request contextual information and simply wish to inject the same data into every policy request, you can use the ConstantContextDataProvider implementation included with the OPA Spring Boot SDK.

You can find an example of a custom ContextDataProvider here, in the opa-springboot-demo. This example considers the use case of requiring certain extra roles to be possessed by a user based on which resource (servlet path) is being accessed. In this case, it bases this on the content of the request path, but in a real-world use case this would likely be a database query or an API call to some specialized service.