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:
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:
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";
}
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 {}
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 {}
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: Bothcompanyandofficerroles can accessSecurityReview: Onlyofficerrole can perform review operationsSecuritySearch: Both roles can perform search operationsSecuritySubmission: Onlycompanyrole 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:
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
VIEWoperation 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.
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 (viaSecurityAll).SUBMIT: Accessible only by users with theCOMPANYrole (viaSecuritySubmission).APPROVEandREJECT: Accessible only by users with theOFFICERrole (viaSecurityReview).
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.