Wiki
Clone wikilimeds-framework / Guide To Authentication & Authorization
By default, the framework does not implement authentication or authorization for HTTP endpoints, because we didn't want to constrain developers to a specific security model. However, a hook exists that allows the addition of a custom security provider that automatically ties in with the module of LimeDS that handles the HTTP endpoints.
This is done by implementing the HttpAuthProvider interface and registering it as an OSGi service. Only one method needs to be implemented:
/** * Check if the request is authorized. * * @param request * The http request to be checked * @param response * The http response object, allows altering the response (in * case the request is not valid, e.g. as error reporting) * @param httpOperation * A descriptor of the HTTP operation to which the request is * sent * * @return An optional object containing authentication information provided * by the system, or <em>empty</em> if the request has not been * authorized. * <p> * The JsonValue containing this authentication information is * automatically added by the framework as the last argument of the * apply-function call. */ Optional<JsonValue> authorizeRequest(HttpServletRequest request, HttpServletResponse response, HttpOperationDescriptor httpOperation);
For example, a simple password protected implementation could be:
@Segment public class TokensProvider implements FunctionalSegment, HttpAuthProvider { private static final String GLOBAL_PASSWORD = "admin"; private static final String TOKEN_ATTRIBUTE = "token"; // Mapping of the valid tokens to the corresponding user id private Map<String, String> activeTokens = new HashMap<>(); @HttpOperation(method = HttpMethod.GET, path = "/tokens") @Override public JsonValue apply(JsonValue... input) throws Exception { JsonValue request = input[1].get("request"); JsonValue response = input[1].get("response"); if (GLOBAL_PASSWORD.equals(request.get("query").getString("password")) && request.get("query").has("userid")) { // Create access token UUID uniqueKey = UUID.randomUUID(); String strToken = Base64.getUrlEncoder().encodeToString(uniqueKey.toString().getBytes()); activeTokens.put(strToken, request.get("query").getString("userid")); // Write the token in the HTTP session response.get("headers").asObject().put("Set-Cookie", TOKEN_ATTRIBUTE + "=" + strToken + ";Path=/; HttpOnly"); // Return empty body return null; } else { throw new ExceptionWithStatus(401); } } @Override public Optional<JsonValue> authorizeRequest(HttpServletRequest request, HttpServletResponse response, HttpOperationDescriptor httpOperation) { // Parse the token from the HTTP request Optional<String> token = parseToken(request); // Look up this token in memory if (token.isPresent() && activeTokens.containsKey(token.get())) { /* * If present, return authentication information, indicating * success. */ return Optional.of(Json.objectBuilder().add("userid", activeTokens.get(token.get())).build()); } else { // If not, write an error HTTP response... response.setHeader("WWW-Authenticate", "session-cookie: " + TOKEN_ATTRIBUTE); try { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Valid credentials are required to access this operation!"); } catch (IOException e) { e.printStackTrace(); } // ... and return an empty optional, indicating failure. return Optional.empty(); } } private Optional<String> parseToken(HttpServletRequest request) { if (request.getHeader("Cookie") != null) { return Arrays.stream(request.getHeader("Cookie").split(";")).map(s -> s.trim()) .filter(s -> s.startsWith(TOKEN_ATTRIBUTE)).map(s -> s.split("=")[1].trim()).findAny(); } return Optional.empty(); } }
As you can see, it implements both the FunctionalSegment and the HttpAuthProvider interface. Being a Segment, we can use LimeDS to expose the function as a HTTP endpoint that gives out tokens to users with a valid userid & password combination. The HttpAuthProvider implementation is picked up by the LimeDS HTTP Manager and the authorizeRequest method is now automatically called each time a HTTP endpoint (implemented as a FunctionalSegment) for which Authentication or Authorization is configured, is called.
Notice that we use Cookies to write the generated token to the Http Session upon successful authentication.
Updated