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. For full usage options, consult the repository README.
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.
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.
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
Configuration properties are defined in OPAProperties
and can be set externally, e.g. via application.properties
, application.yaml
, system properties, or environment variables.
Example application.yaml
:
opa:
url: http://localhost:8182 # OPA server URL. Default is "http://localhost:8181".
path: foo/bar # Policy path in OPA. Default is null.
request:
resource:
type: stomp_endpoint # Type of the request's resource. Default is "endpoint".
context:
type: websocket # Type of the request's context. Default is "http".
subject:
type: oauth2_resource_owner # Type of the request's subject. Default is "java_authentication".
response:
context:
reason-key: de # Key to search for decision reasons in the response. Default is "en".
A "default decision" corresponds to the default_decision
OPA configuration key, see OPA's Configuration doc for more information.
Auto-configuration will be done using OPAAutoConfiguration
with autowired OPAClient
and OPAAuthorizationManager
beans, which can be overridden by custom beans if you need to customize their behavior (not just change their configuration).
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 {
@Autowired
OPAAuthorizationManager opaAuthorizationManager;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize.anyRequest().access(opaAuthorizationManager));
// Other security configs
return http.build();
}
}
Here are some examples of adding an OPAAuthorizationManager
to a Spring security configuration:
Injecting Application-Specific Data using OPA Input Customizers
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 or override the input schema completely.
The top level input
contains a tuple of (subject
, resource
, action
, and context
). To override any of these, the SDK exposes four beans that, when defined, override their corresponding parts of the input schema:
OPAInputSubjectCustomizer
OPAInputResourceCustomizer
OPAInputActionCustomizer
OPAInputContextCustomizer
After applying input
customization, input
will be validated to ensure it at least contains these
keys with not-null values:
resource.[type, id]
action.name
subject.[type, id]
context.type
, ifcontext
exists
OPAInputSubjectCustomizer
Clients could define an OPAInputSubjectCustomizer
bean to customize the subject
part of the input
. subject
map
must at least contain type
and id
keys with not-null values, though their values could be modified.
Example OPAInputSubjectCustomizer
bean:
import static com.styra.opa.springboot.input.InputConstants.SUBJECT;
import static com.styra.opa.springboot.input.InputConstants.SUBJECT_AUTHORITIES;
import static com.styra.opa.springboot.input.InputConstants.SUBJECT_TYPE;
@Configuration
public class OPAConfig {
@Bean
public OPAInputSubjectCustomizer opaInputSubjectCustomizer() {
return (authentication, requestAuthorizationContext, subject) -> {
var customSubject = new HashMap<>(subject);
customSubject.remove(SUBJECT_AUTHORITIES); // Remove an existing attribute.
customSubject.put(SUBJECT_TYPE, "oauth2_resource_owner"); // Change an existing attribute.
customSubject.put("subject_key", "subject_value"); // Add a new attribute.
return customSubject;
};
}
}
OPAInputResourceCustomizer
Clients could define an OPAInputResourceCustomizer
bean to customize the resource
part of the input
. resource
map must at least contain type
and id
keys with not-null values, though their values could be modified.
Example OPAInputResourceCustomizer
bean:
import static com.styra.opa.springboot.input.InputConstants.RESOURCE;
import static com.styra.opa.springboot.input.InputConstants.RESOURCE_TYPE;
@Configuration
public class OPAConfig {
@Bean
public OPAInputResourceCustomizer opaInputResourceCustomizer() {
return (authentication, requestAuthorizationContext, resource) -> {
var customResource = new HashMap<>(resource);
customResource.put(RESOURCE_TYPE, "stomp_endpoint"); // Change an existing attribute.
customResource.put("resource_key", "resource_value"); // Add a new attribute.
return customResource;
};
}
}
OPAInputActionCustomizer
Clients could define an OPAInputActionCustomizer
bean to customize the action
part of the input
. action
map
must at least contain name
key with a not-null value, though its value could be modified.
Example OPAInputActionCustomizer
bean:
import static com.styra.opa.springboot.input.InputConstants.ACTION;
import static com.styra.opa.springboot.input.InputConstants.ACTION_HEADERS;
import static com.styra.opa.springboot.input.InputConstants.ACTION_NAME;
@Configuration
public class OPAConfig {
@Bean
public OPAInputActionCustomizer opaInputActionCustomizer() {
return (authentication, requestAuthorizationContext, action) -> {
var customAction = new HashMap<>(action);
customAction.remove(ACTION_HEADERS); // Remove an existing attribute.
customAction.put(ACTION_NAME, "read"); // Change an existing attribute.
customAction.put("action_key", "action_value"); // Add a new attribute.
return customAction;
};
}
}
OPAInputContextCustomizer
Clients could define an OPAInputContextCustomizer
bean to customize the context
part of the input
. context
map
could be null; however if it is not-null, it must at least contain type
key with a not-null value, though its value
could be modified.
Example OPAInputContextCustomizer
bean which makes context
null (removes it from input
map):
import static com.styra.opa.springboot.input.InputConstants.CONTEXT;
import static com.styra.opa.springboot.input.InputConstants.CONTEXT_TYPE;
@Configuration
public class OPAConfig {
@Bean
public OPAInputContextCustomizer opaInputContextCustomizer() {
return (authentication, requestAuthorizationContext, context) -> null;
}
}