【问题标题】:Oauth2 / Password flow / check permission for a specific entityOauth2 / 密码流 / 检查特定实体的权限
【发布时间】:2016-10-10 16:56:39
【问题描述】:

我的API中的主要数据信息和信息链接到一个项目(Entity), 什么是密码流的好方法:使用 spring security 和 OAuth2 管理与项目相关的特定权限?

在这个应用程序中你有 5 个微服务

  • UAA 微服务:授权服务器
  • 目录微服务
  • 订购微服务
  • 发票微服务
  • 客户微服务

缩放权限:

每个用户可以拥有多个项目,并且可以拥有每个项目的权限:

  • CAN_MANAGE_CATALOG
  • CAN_VIEW_CATALOG
  • CAN_MANAGE_ORDER
  • CAN_VIEW_ORDER
  • CAN_MANAGE_INVOICE
  • CAN_VIEW_INVOICE
  • ...

我有很多想法,但我不确定我是否有好的方法:

用例:我想保护端点:

http://catalog-service/{project_key}/catalogs

只有拥有项目 {project_key} 的 VIEW_CATALOG OR MANAGE_CATALOG 权限的 USER 才能列出项目中存在的所有目录

我的第一个想法:使用带有预授权的 ProjectAccessExpression

CatalogController.java

@Controller
public class CatalogController {
     @PreAuthorize("@projectAccessExpression.hasPermission(#projectKey, 'manageCatalog', principal)" +
        " or @projectAccessExpression.hasPermission(#projectKey,  'viewCatalog', principal)")
    @RequestMapping(
            value = "/{projectKey}/catalogs",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public @ResponseBody List<Catalog> findByProject(@PathVariable("projectKey") String projectKey) {
        return catalogService.find();
    }
}

ProjectAccessExpression.java

@Component
public class ProjectAccessExpression {

        private RestTemplate restTemplate;
        public boolean havePermission(String projectKey, String permission , String username) {
            Boolean havePermission = restTemplate.getForObject(String.format("http://uaa-service/permission/check?project=%1&permission=%2&username=%3",
                    projectKey, permission, username
                    ), Boolean.class);
            return havePermission;
        }
}

不方便:每次都需要调用UAA服务

第二个想法:使用 USER_ROLE

使用用户角色

  • 用户名 |角色
  • mylogin1 | SHOP1.CAN_MANAGE_CATALOG
  • mylogin1 | SHOP1.CAN_VIEW_CATALOG
  • mylogin1 | SHOP2.CAN_MANAGE_CATALOG
  • mylogin1 | SHOP2.CAN_VIEW_CATALOG
  • mylogin1 | SHOP2.CAN_MANAGE_ORDER
  • mylogin1 | SHOP2.CAN_VIEW_ORDER
  • ...

SHOP1SHOP2 是 projectKey

不方便:我不确定,但如果用户更改权限,我需要撤销所有令牌关联

第三个想法:在身份验证blob中添加特定权限

我不知道如何存储...

并在控制器中添加注释:

@PreAuthorize("@ProjectAccessExpression.hasPermission(authentication, 'manageCatalog||viewCatalog', #projectKey)

不方便:第二个想法同样不方便

【问题讨论】:

    标签: java spring spring-security spring-boot spring-security-oauth2


    【解决方案1】:

    基本上看起来您只是在尝试为您的项目利用 OAuth 2.0 的角色。以下是有关 OAuth 2.0 的一些春季文档的摘录

    将用户角色映射到范围:http://projects.spring.io/spring-security-oauth/docs/oauth2.html

    有时不仅通过分配给客户端的范围来限制令牌的范围,而且根据用户自己的权限来限制令牌的范围很有用。如果您在 AuthorizationEndpoint 中使用 DefaultOAuth2RequestFactory,则可以设置标志 checkUserScopes=true 以将允许的范围限制为仅与用户角色匹配的范围。您还可以将 OAuth2RequestFactory 注入到 TokenEndpoint 中,但只有在您还安装了 TokenEndpointAuthenticationFilter 时才有效(即使用密码授予) - 您只需要在 HTTP BasicAuthenticationFilter 之后添加该过滤器。当然,您也可以实现自己的规则来将作用域映射到角色并安装您自己的 OAuth2RequestFactory 版本。 AuthorizationServerEndpointsConfigurer 允许您注入自定义 OAuth2RequestFactory 以便在使用 @EnableAuthorizationServer 时可以使用该功能设置工厂。

    所有这一切基本上归结为您可以通过将范围映射到您自己的自定义角色来保护具有不同范围的端点。这将使您的安全性得到真正的细化。

    我找到了一个很好的演练,您可以用作参考:(显然您必须根据自己的用例配置设置)

    https://raymondhlee.wordpress.com/2014/12/21/implementing-oauth2-with-spring-security/

    【讨论】:

    • 嗨 Matthew,首先感谢您的回答,但我不知道您的链接或章节如何帮助您了解方法或解决此问题“将用户角色映射到范围”raymondhlee.wordpress.com/2014/12/21/…。你有什么用例吗?
    • 真正的诀窍是您希望应用程序的不同区域具有细粒度的安全性。因此,您可以为每个应用程序区域设置自定义范围,然后将不同的用户角色映射到不同的范围。这将为您提供非常精细的控制,因为您可以设置基本上无限的级别组合并将它们应用于不同的应用程序区域。 “实现您自己的将范围映射到角色的规则”
    • 好的,Matthew,我认为这只是一个基本方案,这个例子没有展示如何根据我的问题动态定义特定的授权。示例 ROLE_MANAGEPRODUCT_{PROJECTKEY} ,项目密钥由用户创建。
    • 根据您需要提供自己的自定义 OAuth2RequestFactory 的文档,因为这将允许您设置自定义映射。如果您认为这可以使您足够细化,则另一种选择是设置自定义令牌授予者。
    【解决方案2】:

    这个解决方案我使用并且工作正常

    ** 1 - 用户签名时加载业务逻辑安全性**

    这个例子在数据库中找到具有角色持久化的用户,并添加所有角色依赖项目。操作后我有身份验证令牌 GrantedAuthority : ROLE_USER, ROLE_MANAGE_CATALOG:project1, ROLE_VIEW_PROFILE:project1, ROLE_MANAGE_PROJECT:project2, ...

    @Service
    public class CustomUserDetailsService implements UserDetailsService {
     @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            Optional<User> user = userService.findByLogin(username);
    
            if (!user.isPresent()) {
                Object args[] = {username};
                throw new UsernameNotFoundException(
                messageSource.getMessage("user.notexist", args, "User {0} doesn't not exist", LocaleContextHolder.getLocale())
                );
            }
            if (!user.get().isActivated()) {
                //throw new UserNotActivatedException(String.format("User %s was not activated!", username));
                Object args[] = {username};
                throw new UserNotActivatedException(
                        messageSource.getMessage("user.notactivated", args, "User {0} was not activated", LocaleContextHolder.getLocale()));
            }
            //Here implement your proper logic
            //Add busness logic security Roles
            // eg ROLE_MANAGE_PROJECT:{project_key}, ROLE_MANAGE_CATALOG:{project_key}
            List<Role> bRoles = projectService.getRolesForUser(username)
            user.get().getRoles().addAll(
                bRoles
                );
    
            UserRepositoryUserDetails userDetails = new UserRepositoryUserDetails(user.get());
            return userDetails;
        }
    }
    

    ** 2 使用预授权表达式检查安全性 **

    在本例中,只有拥有此权限的用户才能执行此操作:

    1. ROLE_ADMIN 或
    2. ROLE_MANAGE_PROJECT:{projectKey}

      @PreAuthorize("@oauthUserAccess.hasPermission(authentication, '"+Constants.PP_MANAGE_PROJECT+"', #projectKey)") @请求映射( value="/projects/{projectKey}", 方法 = RequestMethod.PUT, 产生 = MediaType.APPLICATION_JSON_VALUE ) public ResponseEntity updateProject(@PathVariable("projectKey") String projectKey,@Valid @RequestBody 项目项目)

    OauthUserAccess 类:

    @Component("oauthUserAccess")
    public class OauthUserAccess {
    
        /**
         * Check if it is the administrator of the application IMASTER
         * @param authentication
         * @param projectKey
         * @return
         */
        public boolean hasAdminPermission(OAuth2Authentication authentication, String projectKey) {
            if(authentication.getOAuth2Request().getAuthorities().contains("ROLE_ADMIN")) return true;
            return false;
        }
        /**
         * 
         * @param authentication
         * @param permissionType
         * @param projectKey
         * @return
         */
        public boolean hasPermission(OAuth2Authentication authentication, String permissionType, String projectKey) {
            if (!ProjectPermissionType.exist(permissionType) ||
                    projectKey.isEmpty() ||
                    !projectKey.matches(Constants.PROJECT_REGEX))
                return false;
            if (authentication.isClientOnly()) {
                //TODO check scope permission
                if(authentication.getOAuth2Request().getScope().contains(permissionType+":"+projectKey)) return true;
            }
            if (hasAdminPermission(authentication, projectKey)) return true;
            String projectPermission = "ROLE_" + permissionType + ":" + projectKey;
            String projectPermissionManage = "ROLE_" + permissionType.replace("VIEW", "MANAGE") + ":" + projectKey;
            String manageProject = "ROLE_" + Constants.PP_MANAGE_PROJECT + ":" + projectKey;
            Predicate<GrantedAuthority> p = r -> r.getAuthority().equals(projectPermission) || r.getAuthority().equals(projectPermissionManage) || r.getAuthority().equals(manageProject);
    
            if (authentication.getAuthorities().stream().anyMatch(p)) {
                return true;
            };
           return false;
        }
    
    }
    

    3 - 优势/劣势

    优势

    业务逻辑权限只在用户登录应用时加载,而不是每次都加载,是微服务架构的强大解决方案。

    缺点

    需要更新身份验证令牌或在权限更改时撤销令牌 else 当您更新用户的权限时,用户需要注销和登录。但是如果没有此安全逻辑,您也会遇到同样的问题,例如当用户被禁用或启用时。

    我在控制器中使用的解决方案:

    newAuthorities = projectService.getRolesForUser(username);
    UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), newAuthorities);
            OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();
            Collection<OAuth2AccessToken> accessTokens = tokenStore.findTokensByUserName(principal.getName());
            OAuth2Authentication auth2 = new OAuth2Authentication(authentication.getOAuth2Request(), newAuth);
    
            accessTokens.forEach(token -> {
                if (!token.isExpired()) {
                    tokenStore.storeAccessToken(token, auth2);
                }
            });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-04
      • 2018-03-13
      • 1970-01-01
      • 2010-09-25
      • 2020-05-05
      • 2020-11-05
      • 2019-07-13
      • 1970-01-01
      相关资源
      最近更新 更多