OWASP API Security Top 3: Tackling Broken Object Property Level Authorization in Spring Boot 3.x

Master Spring Ter
6 min readNov 9, 2024

With the explosive growth of APIs, security vulnerabilities have become a significant concern, leading to severe data breaches and financial losses. The OWASP (Open Web Application Security Project) API Security Top 10 is a definitive list highlighting the most critical security risks to web APIs. Ranked third in the 2023 list is Broken Object Property Level Authorization (BOPLA), a vulnerability that can grant unauthorized access to sensitive data or allow unauthorized actions on specific object properties within an API.

In this article, we’ll explore what BOPLA is, why it’s imperative to address it, and how to secure your API against it using Spring Boot 3.x.

What is Broken Object Property Level Authorization?

Broken Object Property Level Authorization occurs when an API endpoint authorizes access to an overall resource but fails to enforce security on specific properties within that object. Essentially, while a user may have legitimate access to an object, they might inadvertently gain access to restricted properties they shouldn’t see or modify.

For example, in an e-commerce API, a customer may have access to view a PurchaseOrder object but shouldn’t see sensitive properties like PurchaseOrder.internalDiscountRate if it includes confidential business information. Improper authorization at the property level could lead to data leaks or abuse of privileges.

Common Scenarios of BOPLA Vulnerabilities:

  1. Sensitive Information Exposure: Certain fields or properties might contain sensitive data that unauthorized users should not access, such as financial details, personal identification numbers, or internal metrics.
  2. Unauthorized Modifications: Users might manipulate fields or properties they shouldn’t have permission to change, potentially leading to data corruption or privilege escalation.

Why BOPLA is a Critical Risk

BOPLA vulnerabilities can have severe implications, including:

  • Data Exposure: Sensitive data exposure can result in breaches of privacy and potential legal ramifications.
  • Data Integrity: Unauthorized modifications to object properties can lead to data inconsistency and potential application malfunctions.
  • Compliance Risks: Failing to protect sensitive data at the property level can lead to non-compliance with regulations like GDPR, CCPA, and HIPAA.

Given these risks, it’s essential to implement property-level access control in your Spring Boot APIs to ensure comprehensive security.

Implementing a Solution in Spring Boot 3.x

Securing your Spring Boot 3.x API against BOPLA requires enforcing property-level access control. Here’s a step-by-step guide on how to implement these controls effectively, using innovative naming conventions for better code readability and maintainability.

Step 1: Define Access Control Requirements

Begin by defining authorization rules for each property of an object. For example:

  • Customers can access general information but not sensitive fields.
  • Managers can access all fields within the object.
  • Auditors can access specific fields required for auditing purposes.

These rules should be based on your application’s security requirements and compliance obligations.

Step 2: Use @JsonView for Conditional Serialization

Spring Boot leverages Jackson for JSON serialization. Jackson’s @JsonView annotation allows you to control which fields are serialized based on a view class.

First, define view interfaces representing different access levels:

public class AccessProfiles {
public interface CustomerView {}
public interface ManagerView extends CustomerView {}
public interface AuditorView extends CustomerView {}
}

Then, annotate your model fields with the appropriate views:

import com.fasterxml.jackson.annotation.JsonView;

public class PurchaseOrder {

@JsonView(AccessProfiles.CustomerView.class)
private Long orderNumber;

@JsonView(AccessProfiles.ManagerView.class)
private BigDecimal internalDiscountRate;

@JsonView(AccessProfiles.CustomerView.class)
private String productDescription;

@JsonView(AccessProfiles.AuditorView.class)
private LocalDateTime auditTimestamp;

// Constructors, getters, and setters
}

In this example:

  • Fields accessible to Customers are part of the CustomerView.
  • Fields accessible to Managers (including all Customer fields) are part of the ManagerView.
  • Fields accessible to Auditors are part of the AuditorView.

Step 3: Configure Controllers to Use the Appropriate View

In your controller methods, specify the view to be used based on the user’s role.

First, update your controller to determine the user’s roles and set the view accordingly:

import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/purchase-orders")
public class PurchaseOrderController {

@Autowired
private UserIdentityService userIdentityService;

@Autowired
private PurchaseOrderService purchaseOrderService;

@GetMapping("/{orderNumber}")
public ResponseEntity<MappingJacksonValue> getPurchaseOrder(@PathVariable Long orderNumber, Principal principal) {
UserIdentity currentUser = userIdentityService.getUserByPrincipal(principal);
PurchaseOrder purchaseOrder = purchaseOrderService.getPurchaseOrderById(orderNumber);

// Determine the view based on user roles
Class<?> jsonView;

if (currentUser.hasRole("ROLE_MANAGER")) {
jsonView = AccessProfiles.ManagerView.class;
} else if (currentUser.hasRole("ROLE_AUDITOR")) {
jsonView = AccessProfiles.AuditorView.class;
} else {
jsonView = AccessProfiles.CustomerView.class;
}

MappingJacksonValue mapping = new MappingJacksonValue(purchaseOrder);
mapping.setSerializationView(jsonView);

return ResponseEntity.ok(mapping);
}
}

Here, we’re using MappingJacksonValue to specify the serialization view dynamically based on the user's role.

Step 4: Ensure Proper Deserialization Controls

For write operations, you need to ensure that users cannot modify properties they are not authorized to change. You can use @JsonView on setter methods or fields to control deserialization as well.

For example:

import com.fasterxml.jackson.annotation.JsonView;

public class PurchaseOrder {

// Fields and getters

@JsonView(AccessProfiles.ManagerView.class)
public void setInternalDiscountRate(BigDecimal internalDiscountRate) {
this.internalDiscountRate = internalDiscountRate;
}

// Other setters
}

This ensures that only data sent with the ManagerView can set the internalDiscountRate field.

Alternatively, you can implement validation in your service layer to enforce property-level write permissions.

Step 5: Add Tests for Property-Level Authorization

Write unit tests and integration tests to verify that the property-level authorization behaves as expected.

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

// ...

@Test
public void testManagerCanAccessInternalDiscountRate() throws Exception {
// Mock user with manager role
UserIdentity managerUser = new UserIdentity("managerUser", List.of("ROLE_MANAGER"));
when(userIdentityService.getUserByPrincipal(any(Principal.class))).thenReturn(managerUser);

PurchaseOrder purchaseOrder = new PurchaseOrder(123L, new BigDecimal("0.15"), "Smartphone", LocalDateTime.now());
when(purchaseOrderService.getPurchaseOrderById(123L)).thenReturn(purchaseOrder);

MvcResult result = mockMvc.perform(get("/api/purchase-orders/123"))
.andExpect(status().isOk())
.andReturn();

String response = result.getResponse().getContentAsString();

// Verify that internalDiscountRate is included
assertTrue(response.contains("\"internalDiscountRate\":0.15"));
}

@Test
public void testCustomerCannotAccessInternalDiscountRate() throws Exception {
// Mock user with customer role
UserIdentity customerUser = new UserIdentity("customerUser", List.of("ROLE_CUSTOMER"));
when(userIdentityService.getUserByPrincipal(any(Principal.class))).thenReturn(customerUser);

PurchaseOrder purchaseOrder = new PurchaseOrder(123L, new BigDecimal("0.15"), "Smartphone", LocalDateTime.now());
when(purchaseOrderService.getPurchaseOrderById(123L)).thenReturn(purchaseOrder);

MvcResult result = mockMvc.perform(get("/api/purchase-orders/123"))
.andExpect(status().isOk())
.andReturn();

String response = result.getResponse().getContentAsString();

// Verify that internalDiscountRate is not included
assertFalse(response.contains("internalDiscountRate"));
}

These tests ensure that customers cannot access internalDiscountRate, while managers have full access to the field.

Step 6: Secure API Input for Write Operations

For write operations (e.g., POST, PUT), ensure that users cannot modify properties they are not authorized to change.

You can use @JsonView to control deserialization or implement validation in your service layer.

Example:

@PostMapping("/")
public ResponseEntity<PurchaseOrder> createPurchaseOrder(@RequestBody PurchaseOrder purchaseOrder, Principal principal) {
UserIdentity currentUser = userIdentityService.getUserByPrincipal(principal);

if (!currentUser.hasRole("ROLE_MANAGER")) {
// Customers cannot set internalDiscountRate
purchaseOrder.setInternalDiscountRate(null);
}

PurchaseOrder savedOrder = purchaseOrderService.savePurchaseOrder(purchaseOrder);
return ResponseEntity.status(HttpStatus.CREATED).body(savedOrder);
}

Alternatively, you can use a separate DTO (Data Transfer Object) for input and map only the allowed fields.

Step 7: Implement Custom Security Annotations (Optional)

For more advanced scenarios, you can create custom annotations to control access at the property level.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AccessControl {
String[] rolesAllowed() default {};
}

Apply this annotation to your model fields:

public class PurchaseOrder {

private Long orderNumber;

@AccessControl(rolesAllowed = {"ROLE_MANAGER"})
private BigDecimal internalDiscountRate;

// Other fields, constructors, getters, and setters
}

Then, create a custom serializer or use an aspect-oriented approach to enforce these access controls during serialization and deserialization.

Additional Security Considerations

In addition to implementing property-level authorization, consider these security best practices:

  1. Input Validation: Always validate and sanitize inputs to prevent injection attacks.
  2. Auditing and Logging: Track access attempts to sensitive fields to monitor potential unauthorized access.
  3. Rate Limiting: Protect APIs from excessive calls by limiting requests.
  4. Use HTTPS: Secure all data in transit by enforcing HTTPS.
  5. Use DTOs (Data Transfer Objects): Use DTOs to transfer only the data that the user is authorized to see or modify, avoiding exposure of internal domain models.
  6. Implement Service-Level Security Checks: Do not rely solely on presentation layer controls. Enforce security in your service and data access layers to prevent unauthorized access through alternative channels.
  7. Regular Security Audits: Perform regular security assessments and code reviews to identify and fix vulnerabilities.

Conclusion

Broken Object Property Level Authorization (BOPLA) vulnerabilities can expose sensitive data or allow unauthorized actions, posing significant security risks. By implementing fine-grained, property-level access controls in Spring Boot 3.x, you can effectively mitigate these risks.

In this article, we demonstrated how to design and implement property-level authorization using @JsonView and role-based access control, employing innovative naming conventions for clarity and maintainability. This approach leverages built-in features of Spring Boot and Jackson, providing a robust and scalable solution.

By proactively addressing BOPLA vulnerabilities, you enhance the security of your API and ensure compliance with data protection regulations. Regularly update your authorization logic and thoroughly test each component to prevent unauthorized data exposure in your application.

Remember: Security is not a one-time task but an ongoing commitment. Stay vigilant and keep your applications secure!

Sign up to discover human stories that deepen your understanding of the world.

Master Spring Ter
Master Spring Ter

Written by Master Spring Ter

https://chatgpt.com/g/g-dHq8Bxx92-master-spring-ter Specialized ChatGPT expert in Spring Boot, offering insights and guidance for developers.

No responses yet

Write a response