Securing a Groovy API
TORO Integrate leverages Spring MVC and Spring Security features to implement method-level security on HTTP APIs written using Groovy.
Getting started
Consider the following Groovy code:
1 2 3 4 5 6 7 8 9 | @RequestMapping(value = "lannister") class HouseLannister { @ResponseBody @RequestMapping(value = 'welcome', method = RequestMethod.PUT) def welcome() { return "Welcome home" } } |
This exposes an HTTP endpoint on /api/lannister/welcome
that simply returns "Welcome home"
. We can
verify this by issuing a cURL command:
1 | curl -X PUT 'http://localhost:8080/api/lannister/welcome'
|
This responds with:
1 | Welcome home |
Our goal for this example is to limit access to this endpoint. We can secure this endpoint by applying the
@Secured
annotation.
1 2 3 4 5 6 7 8 9 10 11 12 | import java.security.Principal @RequestMapping(value = "lannister") class HouseLannister { @Secured("Jaime") // Restricts access for a user with username 'Jaime' @ResponseBody @RequestMapping(value = 'welcome', method = RequestMethod.PUT) def welcome( Principal principal ) { return "Welcome home, ${principal.name}" } } |
Resolvable method parameters
The above code also introduces the injection of java.security.Principal
object in the method parameter. This allows us later to query the name of the authenticated user and append it
to the "Welcome home"
response.
Making the cURL command again yields:
1 2 3 4 5 | <APIException> <result>ERROR</result> <apiErrorCode>-1</apiErrorCode> <message>Access is denied</message> </APIException> |
At this point, the endpoint is now secured1. Only a User
with the username Jaime
can
access the endpoint. By using Jaime
's access token retrieved via OAuth 2.0,
we can execute:
1 2 3 | curl -X PUT \ http://localhost:8080/api/lannister/welcome \ -H 'Authorization: Bearer b97e1d2f-28ed-495f-bb56-2538b72c224f' |
... to which the server responds:
1 | Welcome home, Jaime |
Using Basic
TORO Integrate also supports the Basic authentication scheme. Assuming you have it enabled, you can receive the same response with (using your user's configured credentials):
1 2 | curl -X PUT -u Jaime:kingslayer \
http://localhost:8080/api/lannister/welcome
|
Access control lists
When implementing security requirements, it's often more manageable to organize a set of permissions into a single logical group. Consider a requirement change in the previous example, where more users are allowed to access the HTTP endpoint. By creating a group that consists of the users:
- Jaime
- Cersei
- Tyrion
- Tywin
We can assign the group's name on the @Secured
annotation in the code, as in:
1 2 3 4 5 6 7 8 9 10 11 12 | import java.security.Principal @RequestMapping(value = "lannister") class HouseLannister { @Secured("Lannister") // Restricts access for all users under 'Lannister' @ResponseBody @RequestMapping(value = 'welcome', method = RequestMethod.PUT) def welcome( Principal principal ) { return "Welcome home, ${principal.name}" } } |
The endpoint /api/lannister/welcome
then becomes accessible only for authentication requests from registered
users under the group Lannister
.
Class level annotation
Like the @RequestMapping
, you can also add @Secured
at class level. This applies the access restrictions
to all methods in the class.
Security metadata
Aside from the annotation-based configuration, TORO Integrate also supports metadata-based security declarations for Groovy-based services. This approach allows us to decouple security configuration from the code.
Consider again the welcome example:
1 2 3 4 5 6 7 8 9 10 11 | import java.security.Principal @RequestMapping(value = "lannister") class HouseLannister { @ResponseBody @RequestMapping(value = 'welcome', method = RequestMethod.PUT) def welcome( Principal principal ) { return "Welcome home, ${principal.name}" } } |
What changed?
Notice that only the @Secured
annotation was removed.
In the Coder Navigator view, right-click on HouseLannister.groovy
and choose Properties.
This should prompt a dialog2:
Quickly open the Properties dialog
With the service selected, Press to display the Properties dialog.
By adding Lannister
to the Groups column, we once again restrict HTTP access to users under the declared group.
Behind the scenes, this creates a .security
file in the file system (not visible in Coder).
Swagger and OpenAPI support
As of version 3.0, TORO Integrate does not yet support transformation from the security metadata into Swagger or OpenAPI specification. This prevents the API Explorer from using the correct security scheme, and treats the operation as public.
-
In the Server edition, accessing the secured endpoint in the browser (i.e.: GET request) with a user logged-in in the web UI is allowed. This is due to Marketplace credentials containing greater authorization than Integrate users. ↩
-
Coder Cloud has only begun to support the management of security metadata in v1.3. ↩