Swagger + Spring security - 基于角色隐藏方法

Posted

技术标签:

【中文标题】Swagger + Spring security - 基于角色隐藏方法【英文标题】:Swagger + Spring security - Hide methods based on roles 【发布时间】:2017-05-17 15:30:40 【问题描述】:

我有一个拥有不同消费者的 API。我希望他们根据他们在 Spring Security 中的角色获得相关文档。

例如

API 操作 A 仅限于角色 A 和角色 B

API 操作 B 被限制为角色 B

API 操作 C 对所有人开放

我正在使用 SpringFox、Spring 4、Spring Rest、安全性

我知道有一个名为@ApiIgnore 的注释,也许可以使用。

这可能吗?

【问题讨论】:

@ApiIgnore 对您的用例有何用处?它只是阻止显示某些资源......如果您使用带有表的 Spring Security 模型来存储角色、用户、权限等,您可以创建一个端点来检索这些数据。将所有这些创建为资源,因为 Swagger 映射不起作用,因为您需要围绕它们进行真正的 Rest 注释。 好吧,我的意思是,也许我可以编写自己的注释,在不存在角色的情况下使用 apiignore。或类似的东西。 你找到解决办法了吗? 我们最终为每个集成商提供了不同的文档。由于这只是生成 swagger 文档的问题,因此完成了一些创造性的脚本。糟糕的解决方案,但至少解决了。 这能回答你的问题吗? How to trim Swagger docs based on current User Role in Java Spring? 【参考方案1】:

经过一番搜索,我发现在 web.xml 中没有办法解决这个问题。所以我用自己的解决方案解决了。

我编写了一个过滤器,用于修改响应并删除用户无权访问的 api。

过滤器是这样的:

 @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);

要修改回复,您应该关注this link。

然后我们需要对生成的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();

在我的情况下,我有一个AccessHandler,它使用 url 处理访问控制。你应该把这部分写在你的逻辑上。 对于 spring 安全角色,您可以使用如下内容:

request.isUserInRole("Role_A");

【讨论】:

有一种替代方法,它不涉及修改 json 对象。看看这个答案:***.com/a/61860729/285060【参考方案2】:

我已经发布了类似的问题并找到了解决方案。因为我在 *** 上发现了 3 个类似的问题,所以我不知道我是应该在所有这些问题中复制粘贴答案,还是提供我的答案的链接。

解决方案由两部分组成:

    通过 OperationBuilderPlugin 扩展控制器扫描逻辑以保留 Swagger 供应商扩展中的角色 覆盖 ServiceModelToSwagger2MapperImpl bean 以根据当前安全上下文过滤掉操作

详情请见:https://***.com/a/61860729/285060

【讨论】:

【参考方案3】:

块引用 您可以在安全配置文件中使用以下代码 sn-p,并且需要扩展 GlobalMethodSecurityConfiguration。

@自动连线 Auth2ServerConfiguration auth2ServerConfiguration;

 @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() 
        return new OAuth2MethodSecurityExpressionHandler();
    

在API中使用如下代码

@PreAuthorize("hasRole('ROLE_ADMIN') and hasRole('ROLE_USER')")
@Transactional(readOnly = true)
 public @ResponseBody ModelAndView abc()  
    //do something
  

【讨论】:

【参考方案4】:

您可能已经看到了这一点,但 SpringFox 本身提供了配置安全性的机制。请参阅 SpringFox 官方文档中的 this section 和 this section 示例(注意点 #14 和 #15)。

如果您愿意允许不同的消费者查看 API,但仍然无法执行 API,您可以考虑在具有适当角色的 API 上添加 @Secured 注释。

例如:

@Secured ("ROLE_A", "ROLE_B")
@RequestMapping ("/open/to/both")
public String operationA() 
    // do something


@Secured ("ROLE_B")
@RequestMapping ("/open/to/b/only")
public String operationB() 
    // do something


// No @Secured annotation here
@RequestMapping ("/open/to/all")
public String operationC() 
    // do something

确保您已在您的 SecurityConfig 类(或任何您拥有的类)中添加了 @EnableGlobalMethodSecurity (securedEnabled = true),以便 @Secured 正常工作。

【讨论】:

感谢您的回答,但这并不能解决我的问题。我已经使用角色保护了我的应用程序,我希望文档考虑到这一点。 @EspenSchulstad 您找到解决问题的方法了吗?谢谢

以上是关于Swagger + Spring security - 基于角色隐藏方法的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Security 启用 Swagger springdoc-openapi-ui (OpenAPI 3.0) - 无法访问 swagger-ui.html (401)

如何在 Swagger-ui 中显示 Spring Security 用户名密码身份验证过滤器 url?

How to configure Spring Security to allow Swagger URL to be accessed without authentication

Spring Boot Security - 如果用户未通过 Oauth2 进行身份验证,如何禁用对 swagger ui 页面的端点的访问

swagger,swagger-beauty,security,rbac-shiro

Angular集成Spring Boot,Spring Security,JWT和CORS