Switch to dark theme

Switch to light theme

Enforce access policies in SpringBoot #

Background #

As a developer who is offering APIs, you have to ensure that only authorized applications or services can consume your APIs. It is common practice to use tokens to enforce access policies in such scenarios. If the caller has a valid token, the API performs the promised functionality. If not, the API returns a 401, unauthorized error.

If you are using SpringBoot, you can use Cipher EIAM in three simple steps to enforce access policies in your APIs. Applications and services that want to invoke your APIs will use JWT tokens issued by Cipher EIAM on your behalf. You can either choose to issue these JWT tokens yourself, or you can enable your consumers (developers of applications and services) to generate these JWT tokens themselves.

For example- let’s assume you want to execute an API only if the requester is an admin and has the privilege to perform update action on the report catalog object. For this, you can create a bot in Cipher EIAM to generate a JWT token by setting the role as admin and scope as admin. On the sandbox side, you need to create an admin role and map it to the role entry with an update action on the report_catalog object. After the creation of roles, objects and actions are completed, you will need to enforce these access policies in your spring boot application so that your APIs can know whether the end-employee who is making this API request with the above JWT token has the required privileges or not to perform the update action on the report catalog object.

Prerequisite #

Before you start enforcing policies in your spring boot application, make sure you have completed the below-mentioned steps:

  1. Make sure you have a tenant created in Cipher and that you also have access to Authentication Center. Check out detailed steps here.
  2. A domain is created and a basic sign-in process is defined with email, phone etc. You can refer here for detailed steps.
  3. Create a bot that will be used to generate JWT tokens. You can refer here for detailed steps.
  4. You have created a sandbox and defined all the access management components inside it like object types, object groups, actions, action groups, role entries and roles based on your business requirement. If not, then you can refer to this link for getting started

Step 1: Import libraries #

For importing all the required set of libraries, you can attach the below code snippet in the pom.xml file of your spring boot application.

Switch Theme
Expand More
Copy
	<dependency>
	<groupId>in.zeta</groupId>
	<artifactId>spring-boot-commons</artifactId>
	<version>1.0.18-SNAPSHOT</version>
	</dependency>
Code Copied

Running this code snippet will automatically download all the required libraries

Step 2: Implementing ObjectProvider #

After completing the above step, you will need to create an ObjectProvider. The reason behind this is that in order to check whether any employee has the privilege to perform requested action on the object, certain rules have to be applied to the requested object. And for this reason, the ObjectProvider interface needs to be implemented on the custom object that you have defined.

You can refer to the below code snippet for creating your ObjectProvider.

Switch Theme
Expand More
Copy
	@Service
	public class ReportCatalogService implements ObjectProvider<ReportCatalog> {

	   @Autowired
	   public ReportCatalogService(SandboxAccessControlProvider sandboxAccessControlProvider) {
	       sandboxAccessControlProvider.registerObjectProvider("report_catalog", this);
	   }

	   @Override
	   public CompletionStage<Optional<ReportCatalog>> getObject(JID objectJID, Realm authenticationRealm, Long tenantID) {
	       return CompletableFuture.completedFuture(getReportCatalog(tenantID, objectJID.getNodeId()));
	   }

	   public Optional<ReportCatalog> getReportCatalog(Long tenantID, String reportCatalogID) {
	       return Optional.of(new ReportCatalog(reportCatalogID, "sandbox " + reportCatalogID));
	   }
	}
Code Copied

In this example, ReportCatalogService represents the object for which we are creating the ObjectProvider. When you will be using annotations on your functions (explained in the next step), this object provider code is executed by Cipher in the backend to check the requester’s privileges

Step 3: Adding annotations to your function #

After you have completed the previous steps, you just need to add an annotation to your function. The annotation will take 3 inputs: action, object and tenant id. Annotations are added to ensure that the function will be accessible to only those end-employees who have the privilege to perform the specific action on the specific object. You can refer to the below code snippet:

Switch Theme
Expand More
Copy
	@SandboxAuthorizedSync(action ="addReport", object = "$$report_catalogs$$@report_catalog.zeta.in", tenantID = "$$tenants$$")
	@PatchMapping(value = "/tenants/{tenantID}/report_catalogs/{reportCatalogID}/reports")
	public ResponseEntity<GetReportResponse> addReport(@PathVariable String tenantId,  @PathVariable String reportCatalogID, @RequestBody CreateReportCatalog request) {
	}
Code Copied

Through the above code, the developer is trying to ensure that the addReport method will be accessible to only those end-employees who have the privilege to add reports. And the process of validating these details will be performed by the ReportCatalogService ObjectProvider.

Enabling multiple actions in a single function #

Under some use cases, you might need to perform multiple actions using a single function. For eg- let’s say you want to create a single function to update the status of your credit card to enable, disable or block. By using the above annotation process, you will have to create three separate functions and annotations to enable, disable and block the card. In order to achieve this using single function, please use the below annotation format:

Switch Theme
Expand More
Copy
	@PatchMapping(value = "/tenants/{tenantId}/cards/{cardId}")
	@SandboxAuthorizedSync(
	privileges={ 
	@Privilege (action = "cardStatus.update."+"$$status$$", object = "$$cards$$@" + ".cipher.app"),
	@Privilege (action = "cardStatus.update", object = "$$cards$$@" + ".cipher.app")
	}, tenantID = "$$tenants$$" )
	public ResponseEntity<GetCardResponse> updateCard(@PathVariable String tenantId,@PathVariable String cardId,@RequestBody UpdateCardRequest request) {
	}
Code Copied

By adding the status variable in the action field, you can specify dynamic actions that you want your function to support on the object. For example: if you want your function to allow blocking of cards, then you can set the value of the action field to cardStatus.update.block by path variables or request payload and achieve the same.

Next Steps #

If you are looking for a test application using these annotations for your reference, please click here.