Developer Docs

Security handling with SpringBot server-side

One of Codebots’ highest priorities is the security of the software which our codebots write, to the point where a diagram was made to focus on that sole purpose. This same level of care and attention was given to developing SpringBot’s security strategy.

When it comes to security, we categorise all work into the three A’s:

SpringBot utilises the standard Spring Security framework to cover Authentication and Authorisation, and Hibernate Envers for Auditing purposes. All of which has been implemented on top of our security standard which ensures all third generation bots have the same level of security across the software stack.

This article will explore SpringBot’s security strategy, focusing on how to customise the three A’s within your application.

We recommend all readers also explore the security strategy for SpringBot, which has been documented in the security documentation section of your Reference Library.

Authentication

In regards to Authentication, there are three main files that are responsible for creating, verifying and destroying authentication tokens. Each file and its purpose has been outlined below.

File Name Purpose
JWTLoginFilter.java Authenticates the user during login using their username and password to create a user token that can be used to authenticate for future requests.
JWTLogoutFilter.java Invalidates the users token so that it can no longer be used for authentication attempts.
JWTAuthenticationFilter.java Attempts to authenticate and return a success or unsuccessful response based upon the provided user token.

All of the files are located at the following relative path: serverside/src/main/java/[ProjectName]/configs/security/filters/[FileName]

Authorisation

SpringBot uses method level security through the use of the @PreAuthorize annotation. This annotation takes the form of @PreAuthorize("hasPermission('[Entity]', 'read')"), and has been applied to service and controller methods throughout SpringBot (although it can be applied almost any bean).

Whenever an annotated method is called, CustomPermissionEvaluator is invoked to check whether the current user has permission to perform the action detailed in the annotation (i.e. read). This file can be found at serverside/src/main/java/[ProjectName]/configs/security/evaluators/CustomPermissionEvaluator.java.

The best way to modify roles and privileges is through the Security Diagram.

More details on how the method-based authorisation works can be found in the Spring documentation.

Auditing

By default, auditing is enabled and can be used to track the changes of the data within the application. Such changes are logged in two different tables: the EntityAudit and Revisions tables, which have been created and managed by Hibernate Envers.

An entity can be registered for auditing via Envers annotation @Audited, which will inform Envers to create the necessary audit tables and keep track of changes to any instance of that entity automatically. All entities within the application can be found under serverside/src/main/java/[projectName]/entities/*.

The data structures involved in auditing can be viewed in the below:

These entities don’t actually need to be made in the Entity Diagram, it was just used to demonstrate the relationship and entities involved. In all applications, these entities are abstracted and not included in the diagram. Additionally, this diagram represent conceptual relationships not concrete ones.

Image

Revisions and EntityAudit table

Mutations are stored within the Revisions and EntityAudit tables.

The Revisions table records the metadata for a given mutation as a revision. This data includes the timestamp of the mutation, the author, and entity that was changed. The revisions are handled by the Java class CustomRevisionEntity and is located at serverside/src/main/java/[ProjectName]/configs/security/auditing/CustomRevisionEntity.java. You can track additional values by adding to the protected regions in this class.

A full copy of the entity instance is stored in the EntityAudit table for that given entity, along with a reference to the given revision in the revisions table. This allows for the metadata to be stored and associated with the underlying data itself.

The entity audit table follows the naming convention of [EntityName]_entity_audit_log.

Read audits

In addition to auditing the mutations, SpringBot also records read requests and responses.

Read requests and responses are controlled separately.

Request audit logs

Request logging is all controlled by the RequestLoggingFilter which can be found in the file [projectName]/configs/security/filters/RequestLoggingFilter.java.

To disable, enable the protected region Update request logging configuration here found in [projectName]/configs/security/SecurityConfig.java and remove the RequestLoggingFilter bean.

For example:

Enabled

// % protected region % [Update request logging configuration here] off begin
/**
 * Filter used to log request info for every request that reaches the server side.
 */
@Bean
public RequestLoggingFilter logFilter() {
	return new RequestLoggingFilter();
}
// % protected region % [Update request logging configuration here] end

Disabled

// % protected region % [Update request logging configuration here] on begin
// % protected region % [Update request logging configuration here] end

The request is stored in a log file that is archived daily. By default, this log file can be found at serverside/logs and is called request-log.log. The archive of the logs (rotated daily) can be found in the serverside/logs/archived directory.

An example of an entry in this log is:

2019-09-19 13:27:48,506 INFO example_app.configs.security.filters.RequestLoggingFilter [ http-nio-8080-exec-10 ] Method: [POST], URI: [/graphql], User: [staff@example.com], Time: [2019-09-19T13:27:48.505+10:00], Payload: [{"operationName":"GetWithQuery","variables":{"pageIndex":0,"pageSize":10,"orderBy":[{"path":"created","descending":true}],"where":[[]]},"query":"query GetWithQuery($pageIndex: Int, $pageSize: Int, $orderBy: [OrderBy!], $where: [[Where!]!]) {\n timesheets: timesheets(pageIndex: $pageIndex, pageSize: $pageSize, orderBy: $orderBy, where: $where) {\n  ...TimesheetProperties\n  staff {\n   id\n   email\n   __typename\n  }\n  __typename\n }\n totalCount: countTimesheets(where: $where)\n}\n\nfragment TimesheetProperties on Timesheet {\n ...TimesheetBaseProperties\n hoursWorked\n description\n __typename\n}\n\nfragment TimesheetBaseProperties on Timesheet {\n id\n created\n modified\n __typename\n}\n"}], Headers: [{host:localhost:8080,connection:keep-alive,content-length:731,sec-fetch-mode:cors,origin:http://localhost:4200,authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzdGFmZkBleGFtcGxlLmNvbSIsImV4cCI6MTU2ODg2NzI1M30.60Oi_pbsYUUTWpvnZBrb7_FmTqvvTAypSToTjAtsaM90udT-KVVo_HMTyDkbjehmWNwtbfRCEofVP8GbQeyCjA,content-type:application/json,accept:application/json, text/plain, */*,user-agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36,dnt:1,sec-fetch-site:same-site,referer:http://localhost:4200/timesheet,accept-encoding:gzip, deflate, br,accept-language:en-GB,en;q=0.9,en-US;q=0.8,en-AU;q=0.7,}]

Response audit logs

Each entity in your SpringBot application has an associated entity listener which can be found at serverside/src/main/java/[ProjectName]/entities/listeners.

Each of these listeners has a method called afterLoad that records all the data fetched in the associated read audit table (only one exists for each entity). The read audit tables follow the naming convention entity_name_entity_read_audit and stores the created date time, the data, and the user that made the request.

In some cases this functionality can cause performance issues due to the additional database requests that this functionality requires.

Configuration

By default, response audit logs are globally enabled. If required, they can be disabled using the security.read-audit-enabled configuration property that can be found in application.properties.

Enable the surrounding protected region and set this property to false. Alternatively, overriding this property in another profile and setting it to false will disable the response auditing.

For example, response auditing would be disabled in the following case:

# Allow auditing of read requests from the persistence layer
# Disabling this may increase application performance but will result in no audit records for data retrieval.
security.read-audit-enabled=false

When disabled, the read audit tables will still be created, but no data will be logged to them.

Selective enabling

Response auditing can be configured per entity. To control whether an individual entity records response audits, enable the protected region called Override the global configuration for read audits for the [EntityName]Entity here in the EntityListener for your entity (found in [projectName]/entities/listeners/).

Take the following and customise to audit as required:

@PostLoad
public void afterLoad(ArticleEntity entity) throws JsonProcessingException {
	checkPermission(entity.getClass().getSimpleName(), "read");

	// % protected region % [Override the global configuration for read audits for the ArticleEntity here] off begin
	if (securityProperties.isReadAuditEnabled()) {
		// Create a new read record against this entity.
		auditService.createWith(entity);
	}
	// % protected region % [Override the global configuration for read audits for the ArticleEntity here] end

	// % protected region % [Add any custom logic to be executed before the entity has been loaded here] off begin
	// % protected region % [Add any custom logic to be executed before the entity has been loaded here] end
}

For example, the following would result in the ArticleEntity always auditing the response, regardless of the global configuration.

@PostLoad
public void afterLoad(ArticleEntity entity) throws JsonProcessingException {
	checkPermission(entity.getClass().getSimpleName(), "read");

	// % protected region % [Override the global configuration for read audits for the ArticleEntity here] on begin
	auditService.createWith(entity);
	// % protected region % [Override the global configuration for read audits for the ArticleEntity here] end

	// % protected region % [Add any custom logic to be executed before the entity has been loaded here] off begin
	// % protected region % [Add any custom logic to be executed before the entity has been loaded here] end
}

On this page