-
Notifications
You must be signed in to change notification settings - Fork 0
Security
We use JSON WEb Tokens to authenticate users on the platform. An endpoint consumes a username / password couple and produces two tokens :
- accessToken : Allow access to protected endpoints.
-
refreshToken: Allow to fetch a new
accessTokenif it has expired.
Pore information about JWT : https://jwt.io/
Lib used to build / parse JWT : https://github.com/jwtk/jjwt
The following endpoints produces the tokens:
http://host/api/users/authenticate (AccessTokenAPI.authenticate())
The request must contain a Authorization header with the the username / password couple modified as followed:
- concatenation of the two strings, separated by ":"
fred.allen@mail.com:testPassword
- Base 64 enconding
ZnJlZC5hbGxlbkBtYWlsLmNvbTp0ZXN0UGFzc3dvcmQ=
- Prefixing by 'Basic '
Basic ZnJlZC5hbGxlbkBtYWlsLmNvbTp0ZXN0UGFzc3dvcmQ=
For more security, it is wise to encrypt this header. Even more if you are not using HTTPS.
This endpoint returns a object containing the accessToken, the refreshToken, and authenticated user.
{
"accessToken" : "...",
"refreshToken" : "...",
"user" : {"..."}
}- 400 : The user is does not follow the correct format
- 401 : Authentication failed. The header is missing, has a bad prefix or the username / password couple is invalid.
When the user is authenticated, you must join their accessToken in every following requests.
You juste need to add his token in the Authorization header, prefixed by 'Bearer '
ex : Authorization=Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmcmVkLmFsbGVuQG1haWwuY29tIiwiaWF0IjoxNTQ4OTQ5OTQ1LCJleHAiOjE1NDg5NTE3NDV9.I_TzLTGqnp0S0JAccBR9xmjZvyRbcHPZ-01S_7-H-SA
- 400: The header is not correctly formated
- 401: Authentication failed
The verification process is done in the AccessTokenFilter class.
If the user is authenticate itself when sending a request, a new accessToken will also be returned in the header of the response, thanks to the AccessTokenRefreshFilter class.
The following endpoint returns consumes a refreshToken and produces a new accessToken:
http://host/api/users/refresh (AccessTokenAPI.refreshAccessToken())
You need to pass the refreshToken in the request body.
This endpoint returns an object containing the accessToken and the authenticated user.
{
"accessToken" : "...",
"user" : {"..."}
}Following annotations are already present and can be used in any project:
- @ControlPublic: Anyone can access.
- @ControlLoggedIn: Authenticated users can access.
- @ControlAdmin: Admins can access
- @ControlAccessNotification: Check if the authenticated user can access the notification given in pathparam
The usage of these annotations is explained in the next chapters.
To avoid any omission, the application returns an error 500 for every non protected endpoint.
To protect an endpoint, you can annotate it with a annotation you created and check the access rights in ApiControlFilter.
//Annotation creation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ControlAdmin {
}//Annotation of the endpoint
@GET
@ControlAdmin
public void adminOnly(){
}//ApiControlFilter
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (apiUtils.isAnnotationPresent(resourceInfo, ControlAdmin.class)) {
authorization.isAdmin(); // throw AuthorizationException si l'utilisateur n'est pas admin
}
}To add a parameter, we just need to add the name of the pathparam we want to use in the APIControlFilter as the annotation parameter.
First, we crate our annotation, with one or many parameters:
//Annotation creation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ControlAccessNotification {
public String notifID();
}Then, we decorate the method we want to protect and add the pathparam name:
//Annotation of the endpoint
@GET
@Path("/notifications/{id}")
@ControlAccessNotification(notifID = "id")
public Response put(@PathParam("id") Long id) {
//process
}We can now get our notification ID in the APIControlFilter:
public void filter(ContainerRequestContext requestContext) throws IOException {
log.debug("Entering filter()");
// ControlAccessNotification
if (apiUtils.isAnnotationPresent(resourceInfo, ControlAccessNotification.class)) {
ControlAccessNotification annotation = apiUtils.getAnnotation(resourceInfo, ControlAccessNotification.class); // getting the annotation
Long notifID = Long.parseLong(findPathParamValue(annotation.notifID())); // getting the pathparam value
log.debug("ControlAccessNotification for notifID : {}", notifID);
authorization.canAccessNotification(notifService.findByIDHandleNotFound(notifID, currentRequest.isAdmin()));
}
// No annotation
else {
log.error("No control annotation found on the service method");
throw new AuthorizationException(500, "Error while authorizing access to the service");
}
log.debug("Leaving filter()");
}As you may have noticed, the magic method is findPathParamValue(). It finds the pathparam with the given name:
private String findPathParamValue(String pathparam) {
for (Entry<String, List<String>> param : uriInfo.getPathParameters().entrySet()) {
if (param.getKey().equals(pathparam)) {
return param.getValue().get(0);
}
}
log.error("Could not find pathParam with given name for notifID param");
throw new AuthorizationException(500, "Error while authorizing access to the service");
}