Restful API
wangdan0527
Posted on November 12, 2020
Protecting restful APIs using the methodology described in the first part of this post is surely a good thing but not all the resources need to be protected in the same way.
Some resources, in fact, may be more important than others and they may require particular privilege in order to be used.
Let's think about an application that have different roles with different permissions assigned to users. An administrator, for example, may have the need to perform operations that have to be restricted and unaccessible by other roles.
In this case the level of security we had with what we had described in the previous post is not enough. Every user that logs-in, in fact, is treated in the same way.
Providing the authentication is successful and the user receives a valid token from the server, he or she can access without any restriction to all the resources offered by the Server.
How to protect resources based on users permissions
Jersey offers the possibility to specify for each resource the privileges that a user must have in order to access it.
We have already explained the concept of PreMatch, Match, and PostMatch, what we did not say is that in PreMatch phase Jersey allows us th create a Security context for the request is being processed.
This is fundamental and extremely powerful as we are able not only to analise the token and perform all the security checks we have illustrated previously but, also, we can define a custom security context and attach it to the request.
The security context attached will be executed automatically in match phase.
In the filter where we have defined all the logic to generate and check the token (see the part I of this post) we will have access to ContainerRequestContext. This object exposes many information regarding the request, for example, what resource path the client is looking for, very useful to understand if the client is trying to log-in the application or if he/she wants to use a resource, and also gives the possibility to create a security context:
@Override
public void filter(ContainerRequestContext requestContext) {
requestContext.setSecurityContext(
new SecurityContext() {
.....
.....
});
}
Inside our new security context we will need to override 4 functions:
public Principal getUserPrincipal()
public boolean isUserInRole(String roles)
public boolean isSecure()
public String getAuthenticationScheme()
The main important one for our purpose here is the isUserInRole that accepts a string as parameter.
This function will be called during the match phase to check if the permissions required by the resource are satisfied by the request.
The logic inside the isUserInRole is very specific to the application and its requirements but in general in this function we will retrieve the user thanks to the token sent in the request and we will check his or her permissions against the ones passed as parameter.
Now the only thing left is how we are going to assign permissions to the resources.
Jersey uses a particular notation @RolesAllowed({}) that can wrap every API call. This notation accepts a string with all the roles (comma separated) that are allowed to use the resource. This string will be passed during the match phase to the function isUserInRole that will return true if the user has the role(s) to access the resource or false otherwise.
If the function returns false an unauthorised response will be returned to the client denying the access to the resource.
What technology to use to access the database
At this point the resources can be considered protected but what about the access to the data?
Even if the access to the resources is protected, in fact, does not mean that our data are protected too. Resources often, if not always, need to access a database so we have to be sure all our resources are protected from SQL injection attacks.
There are many reasons to use ORM to access the database and we have seen already one on the previous post talking about the cache system and EclipseLink, but security and protection of our data from sql injection attacks is another one.
The SQL that is generated by EclipseLink prevents these types of attacks in two main ways.
Prepared statements. EclipseLink supports (and defaults to) binding all SQL parameters. As the SQL is computed before the parameter is passed in, parameterized statements are an effective mechanism for preventing SQL injection (in most resources on preventing injection attacks, prepared statements is the number one recommended solution).
Escaping single quotes. If the application chooses to have prepared statements turned off, EclipseLink parses parameters for ' and escapes them. The escaping of the ' means that the resulting SQL will no longer pose a threat to the database (using an example of sql injection where the hacker try to inject a drop table statement in a select, the SQL would look something like this: SELECT * FROM users WHERE name = a
;DROP TABLE users;SELECT * from users where name =
a
;
There does exist within EclipseLink a mechanism to allow application developers to query directly using native SQL instead of having EclipseLink generate SQL. Applications executing raw SQL through EclipseLink must guard against SQL injection as they are bypassing the SQL injection defenses employed by EclipseLink .
It should go without saying that it is never recommended to expose the native SQL to an application user.
AngularJs: how to reflect the back-end security on the front-end
Angular JS is a powerful javascript MVC framework for SPA.
Thanks to this framework we can replicate on the UI the same concepts we have seen on the back-end so that UI and back-end are perfectly aligned and consistent.
As seen in the previous post, the back-end during the log-in generates a token and returns it to the client. The UI will have to provide this token to the server for every API call in order to be identified and accepted.
In Angularjs we have the possibility to define interceptors to $httpProvider in the config section of app.js that will add the authentication token to each request automatically.
The next step is to protect the access to pages that are not public and require a log-in in order to be viewed.
In this case we have to configure the $routeProvider for every Controller before the html page gets loaded in order to define that the page requires a log-in to be accessed and viewed.
In Angularjs an event is fired when a navigation take place $rootScope.$on('$routeChangeStart', function (event, current);
When this event is fired we can then check if the page we are navigating to requires the user to be logged-in by checking the configuration defined in the $routeProvider.
It is then possible to replicate the same role based access system we have illustrated for Jersey by creating a custom directive that hides and shows components of a page based on user permission.
Posted on November 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024