Skip to main content

Implementing Security

To ensure that only authenticated and authorized users can access and manage company registrations, we define a security layer in our application.

Role-Based Access Control

We start by specifying a security requirement directly on the CompanyDocument interface using the @SecurityRequirement annotation:

CompanyDocument.java
package com.dev.registration.company.document;
//other imports here...
import com.dev.registration.company.document.security.SecuritySearch;
import com.strategyobject.sokit.extensions.core.annotations.SecurityRequirement;

@SecurityRequirement(
mustAuthenticate = true,
mustAuthorize = true,
rolePattern = "so:companies:{name}")
@Document(...)
public interface CompanyDocument {}

This enforces that users must be both authenticated and authorized to interact with company registrations. The rolePattern allows for role-based access tied to specific company identifiers. The rolePattern uses a placeholder {name} that corresponds to the operation being invoked. The required role for each operation follows this format: so:companies:{operationName}

For example, a possible use case can be:to invoke the create operation, the user must have the role: so:companies:create.

This pattern ensures fine-grained control over who can perform which actions on the electronic document.

Defining Security Interfaces

Inside the folder company-registration-system/document/src/main/java/com/dev/registration/company/document/security, we create four security profiles for various access levels:

SecurityAll.java
package com.dev.registration.company.document.security;

import com.strategyobject.sokit.extensions.core.annotations.SecurityRequirement;

@SecurityRequirement(
mustAuthenticate = true,
mustAuthorize = true,
roles = {
SecurityAll.COMPANY,
SecurityAll.OFFICER,
})
public interface SecurityAll {
String OFFICER = "officer";
String COMPANY = "company";
}
SecurityReview.java
package com.dev.registration.company.document.security;

import com.strategyobject.sokit.extensions.core.annotations.SecurityRequirement;

@SecurityRequirement(
mustAuthenticate = true,
mustAuthorize = true,
roles = {
SecurityAll.OFFICER,
})
public interface SecurityReview {}
SecuritySearch.java
package com.dev.registration.company.document.security;

import com.strategyobject.sokit.extensions.core.annotations.SecurityRequirement;

@SecurityRequirement(
mustAuthenticate = true,
mustAuthorize = true,
roles = {
SecurityAll.COMPANY,
SecurityAll.OFFICER,
})
public interface SecuritySearch {}
SecuritySubmission.java
package com.dev.registration.company.document.security;

import com.strategyobject.sokit.extensions.core.annotations.SecurityRequirement;

@SecurityRequirement(
mustAuthenticate = true,
mustAuthorize = true,
roles = {
SecurityAll.COMPANY,
})
public interface SecuritySubmission {}

Where:

  • SecurityAll: Both company and officer roles can access
  • SecurityReview: Only officer role can perform review operations
  • SecuritySearch: Both roles can perform search operations
  • SecuritySubmission: Only company role can submit registrations.

And by applying SecuritySearch.class to the search section of the document, we complete the security setup for querying company registrations.

Associating Security Classes to Operations

Now that we’ve defined our security classes, it’s time to associate them with the start operations and operations of our company document.

Securing Start Operations

We want the VIEW operation to be accessible by both COMPANY and OFFICER users. We do this by specifying the security attribute directly in the StartOperation definition:

StartOperations.java
package com.dev.registration.company.document;

import com.dev.registration.company.data.dm.CompanyViewDto;
import com.dev.registration.company.document.security.SecurityAll;

import com.strategyobject.sokit.extensions.document.api.annotations.DocumentStartOperations;
import com.strategyobject.sokit.extensions.document.api.annotations.StartOperation;
import com.strategyobject.sokit.extensions.document.api.types.StartOperationType;

@DocumentStartOperations
public enum StartOperations {
@StartOperation(
ordinal = 1,
type = StartOperationType.READ,
summary = "View Company",
description = "View Company",
security = SecurityAll.class,
outputType = CompanyViewDto.class)
VIEW,

...
}

Only the VIEW operation is secured at this level, allowing both roles to access company details.

Securing Document Operations

Each operation that causes a state transition must now be restricted according to the role that is allowed to perform it.

Operations.java
package com.dev.registration.company.document;

import com.dev.registration.company.data.dm.CompanyApproveDto;
import com.dev.registration.company.data.dm.CompanyRejectDto;
import com.dev.registration.company.data.dm.CompanySubmitDto;
import com.dev.registration.company.document.security.SecurityReview;
import com.dev.registration.company.document.security.SecuritySubmission;

import com.strategyobject.sokit.extensions.document.api.annotations.DocumentOperations;
import com.strategyobject.sokit.extensions.document.api.annotations.Operation;

@DocumentOperations
public enum Operations {
@Operation(
ordinal = 1,
summary = "Create Company",
description = "Create Company",
startState = "NIL",
endState = "SUBMITTED",
startOperation = "NEW",
security = SecuritySubmission.class,
inputType = CompanySubmitDto.class)
SUBMIT,

@Operation(
ordinal = 1,
summary = "Approve Company",
description = "Approve Company",
startState = "SUBMITTED",
endState = "APPROVED",
startOperation = "REVIEW",
security = SecurityReview.class,
inputType = CompanyApproveDto.class)
APPROVE,

@Operation(
ordinal = 2,
summary = "Reject Company",
description = "Reject Company",
startState = "SUBMITTED",
endState = "REJECTED",
startOperation = "REVIEW",
security = SecurityReview.class,
inputType = CompanyRejectDto.class)
REJECT,
}

In particular:

  • VIEW: Accessible by both roles (via SecurityAll).
  • SUBMIT: Accessible only by users with the COMPANY role (via SecuritySubmission).
  • APPROVE and REJECT: Accessible only by users with the OFFICER role (via SecurityReview).

Building the project

Now, by running the following command:

./gradlew clean build

SOKit will automatically generate code based on all the annotations and configurations we’ve defined so far. The generated files will be placed inside:

/company-registration-system/document/build/generated/sources/annotationProcessor/java/main/com/dev/registration/company/document/document

Among the generated artifacts, you’ll find:

  • Endpoint resources for each defined operation
  • Event classes that represent state transitions
  • Repository interfaces for managing document persistence
  • etc.

This generated code provides all the necessary scaffolding to expose and manage your document lifecycle with minimal manual effort.