Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swagger + Spring security - Hide methods based on roles

Tags:

I have an API that has different consumers. I'd like them to get relevant documentation based on their roles in Spring Security.

E.g

API operation A is constricted to Role A and Role B

API operation B is constricted to Role B

API operation C is open for all

I'm using SpringFox, Spring 4, Spring Rest, Security

I know there is an annotation called @ApiIgnore, which could perhaps be utilized.

Is this at all possible?

like image 355
Espen Schulstad Avatar asked Jan 03 '17 13:01

Espen Schulstad


People also ask

How do I hide API method in Swagger UI?

By adding this attribute on a controller or action and specifying IgnoreApi = true , it gets hidden from auto-generated documentation. However, this user has to apply this to around 80 controllers.

How do I hide parameters in swagger?

6. Using @ApiParam. @ApiParam is also a Swagger annotation that we can use to specify metadata related to request parameters. We can set the hidden property to true in order to hide any property.

How do I hide models in Swagger UI?

To hide the "Models" section, add defaultModelsExpandDepth: -1 to the Swagger UI configuration code in your index. html . Note the option name uses plural Model*s* not Model . Swagger UI also has many other configuration options that control API documentation rendering.

What is Springfox Swagger2?

Swagger2 is an open source project used to generate the REST API documents for RESTful web services. It provides a user interface to access our RESTful web services via the web browser. To enable the Swagger2 in Spring Boot application, you need to add the following dependencies in our build configurations file.


1 Answers

After a bit of searching I found there are no ways offer for this problem in web. So I solved it with my own solution.

I wrote a filter that modify the response and remove the apis which the user has no access to them.

The filter is something like this:

 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    String url = httpServletRequest.getRequestURI();
        if (url.contains("v2/api-docs")) {
            CharResponseWrapper wrapper = new CharResponseWrapper((HttpServletResponse) response);
            chain.doFilter(httpServletRequest, wrapper);
            refineApiBaseOnACL(wrapper);
            return;
        }
    chain.doFilter(httpServletRequest, response);
}

To modify the response you should follow this link .

Then we need to refine the generated api:

private List<String> httpCommands = List.of("get", "head", "post", "put", "delete", "options", "patch");

public void refineApiBaseOnACL(CharResponseWrapper wrapper) {
    try {
        byte[] bytes = wrapper.getByteArray();

        if (wrapper.getContentType().contains("application/json")) {
            String out = refineContentBaseOnACL(new String(bytes));
            wrapper.getResponse().getOutputStream().write(out.getBytes());
        } else {
            wrapper.getResponse().getOutputStream().write(bytes);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private String refineContentBaseOnACL(String originalContent) {
    JSONObject object = new JSONObject(originalContent);
    JSONObject paths = object.getJSONObject("paths");
    JSONArray tags = object.getJSONArray("tags");

    Iterator keys = paths.keys();
    Set<String> toRemovePath = new HashSet<>();
    Set<Integer> toRemoveTags = new HashSet<>();
    Set<String> tagSet = new HashSet<>();
    while (keys.hasNext()) {
        String key = (String) keys.next();
        String[] split = key.split("/");
        if (!getAccessHandler().checkAccessRest(split[1], split[2]))
            toRemovePath.add(key);
        else {
            for (String httpCommand : httpCommands)
                if (paths.getJSONObject(key).has(httpCommand)) {
                    JSONObject command = paths.getJSONObject(key).getJSONObject(httpCommand);
                    JSONArray tagsArray = command.getJSONArray("tags");
                    for (int i = 0; i < tagsArray.length(); i++)
                        tagSet.add(tagsArray.getString(i));
                }
        }
    }

    for (String key : toRemovePath)
        paths.remove(key);

    for (int i = 0; i < tags.length(); i++)
        if (!tagSet.contains(tags.getJSONObject(i).getString("name")))
            toRemoveTags.add(i);

    List<Integer> sortedTags = new ArrayList<>(toRemoveTags);
    sortedTags.sort(Collections.reverseOrder());
    for (Integer key : sortedTags)
        tags.remove(key);


    Pattern modelPattern = Pattern.compile("\"#/definitions/(.*?)\"");
    Set<String> modelSet = new HashSet<>();
    Matcher matcher = modelPattern.matcher(object.toString());
    while (matcher.find())
        modelSet.add(matcher.group(1));

    JSONObject definitions = object.getJSONObject("definitions");
    Set<String> toRemoveModel = new HashSet<>();
    Iterator definitionModel = definitions.keys();
    while (definitionModel.hasNext()) {
        String definition = (String) definitionModel.next();
        boolean found = false;
        for (String model : modelSet)
            if (definition.equals(model)) {
                found = true;
                break;
            }
        if (!found)
            toRemoveModel.add(definition);
    }

    for (String model : toRemoveModel) {
        definitions.remove(model);
    }

    return object.toString();
}

In my case I have a AccessHandler which handles the access control with the url. You should write this section on your logic. For the spring security roles you can use something like this:

request.isUserInRole("Role_A");
like image 78
Mohsen Avatar answered Sep 22 '22 11:09

Mohsen