Oauth2 /特定实体的密码流/检查权限

时间:2016-06-09 21:02:04

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

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

在此应用程序中,您有 5微服务

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

缩放权限:

UUAMICROSERVICE

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

  • 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或MANAGE_CATALOG的用户才能列出项目中的所有目录

我的第一个想法:使用预授权使用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

使用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
  • ...

SHOP1 SHOP2 是projectKey

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

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

我不知道如何存储...

在控制器中注释:

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

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

2 个答案:

答案 0 :(得分:0)

它基本上看起来像是在尝试利用OAuth 2.0的角色为您的项目。以下是关于OAuth 2.0的一些Spring文档的摘录

将用户角色映射到范围: 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/

答案 1 :(得分:0)

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

** 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)”)     @RequestMapping(             值= “/项目/ {projectKey}”,             method = RequestMethod.PUT,             produce = MediaType.APPLICATION_JSON_VALUE             ) public ResponseEntity updateProject(@PathVariable(“projectKey”)String projectKey,@ Valid @RequestBody Project project)

  3. 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 - 优势/劣势

    <强>优势

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

    <强>缺点

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

    我在解决方案中使用的解决方案:

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