# API 权限

针对业务场景提供API级别、操作分组以及资源类型进行权限控制。

# 使用基础

# scope

scope来源于当前请求携带的身份认证信息。在Spring Security里,对应Authentication中以SCOPE_为前缀的authorities

# role

rolescope相似,也是来源于当前请求携带的身份认证信息。在Spring Security里,对应Authentication中以ROLE_为前缀的authorities

# permission

权限表达式,支持使用||等符号进行且或判断,表达式详细使用见Spring官方文档 (opens new window),具体类可见SecurityExpressionRoot。示例如下:

application.yml

fly:
  security:
    permissions:
      # 定义一个名为 manager 的 permission,表示需要拥有 admin 的 scope 权限或角色
      manger: hasScope('admin') || hasRole('admin')

除了官方支持的表达式外,Fly拓展如下:

拓展类:fly.security.authz.method.ExtendedMethodSecurityExpressionRoot

表达式 说明
hasScope(String scope) 如果拥有该scope,返回true
hasAnyScope(String... scopes) 如果拥有scopes数组中任意一个scope,返回true
hasAllScope(String... scopes) 如果拥有scopes数组中所有scope,返回true
hasPermission(String permission) 如果拥有该permission,返回true
hasAnyPermission(String... permissions) 如果拥有permissions数组中任意一个permission,返回true
hasAllPermission(String... permissions) 如果用户permissions数组中所有permission,返回true

# 授权

# 简单授权

若只需要根据scopepermission来控制API的权限,可以在Controller类或方法上使用fly.core.security.annotation.Secured注解:

import fly.core.security.annotation.Secured;

@RestController
@Secured(scopes = "admin") // 设置类下所有 API 需要 admin scope 权限
public class SecuredController {
    
    // 默认校验 admin scope 权限
    @GetMapping("/") 
    public void def() {
        
    }
 
    // 覆盖类上的 @Secured 条件,表示需要 admin 或 secured_read 中任意一个 scope 权限
    @Secured(anyScopes = {"admin", "secured_read"})
    @GetMapping("")
    public void any() {
        
    }
    
}

注解中属性说明:

属性 说明 类型
scopes 需要拥有所有的scopes才有权限访问 String[]
anyScopes 拥有任意一个scopes即可访问 String[]
orPermissions 必须配合scopes使用,表示需要满足scopes或者所有的permission String[]
orAnyPermissions 必须配合scopes使用,表示需要满足scopes或者其中一个permission String[]
permissions 表示需要满足所有的自定义权限规则,独立于scopes判断 String[]
anyPermissions 表示需要满足任意一个自定义权限规则,独立于scopes判断 String[]

# 操作授权

# 定义操作

Controller类或实现接口的方法上使用@SecuredAction注解定义操作,定义时需要指定操作名,可选指定分组:

// 定义操作名为 name,归属分组为 group1 和 group2 的操作
@SecuredAction(name = "name", groups = {"group1", "group2"})
@GetMapping("")
public void example() {...}

后续可以给操作或操作分组设置所需的权限。框架提供的常用基础操作注解如下:

注解 操作名 归属分组
@AdminAction admin admin
@CreateAction create adminwrite
@DeleteAction delete admin
@UpdateAction update adminwrite
@FindAction find adminread
@QueryAction query adminread

上列常用操作已在Crud接口中使用,如@QueryActionCrudQuery接口中已应用:

public interface CrudQuery<T> {
    
    ...
    @QueryAction
    @GetMapping("")
    default QueryResult<T> query(CrudContext<T> context, Query query) throws Exception {
    	...   
    }
}

因此,当开发了CRUD 接口后,可以直接在实现该接口的Controller类上进行授权操作

# 授权操作

定义操作后,在Controller类上添加@ActionSecurity 注解设置操作或分组所需要的权限:

@RestController
// 为 actionName, groupName 操作或操作分组设置需要 admin scope 权限才能访问
@ActionSecurity(actions = {"actionName", "groupName"}, scopes = "admin")
public class ExampleController {...}

框架对常用操作包装了对应的授权注解:

注解 可授权的操作/分组
@AdminSecurity admin
@DeleteSecurity delete
@ReadSecurity read
@WriteSecurity write

整体使用示例参考:

@RestController
@RequestMapping("/news")
// read 操作/操作分组需要 news_read scope 权限,示例中可控制实现的 CrudAll 中 CrudQuery、CrudFind 接口权限
@ReadSecurity(scopes = "news_read")
// custom1 操作/操作分组需要 admin scope 权限
@ActionSecurity(actions = "custom1", scopes = "admin")
public class NewsController implements CrudAll<NewsEntity> {
    
    // 自定义可授权操作 custome1
    @SecuredAction(name = "custom1")
    @GetMapping("/custom1")
    public void custom1() {
        
    }
    
    // 自定义可授权操作 nonSecured,因无对应的 ActionSecurity,所以该操作实际无权限控制
    @SecuredAction(name = "nonSecured")
    @GetMapping("/custom2")
    public void custom2() {
        
    }
    
}

# 资源授权

资源对应不同的操作及数据范围,如结合上节对 API 进行操作定义,达到对 API 进行权限控制的目的。资源权限结果集来源于以SCOPE_AR_AR_为前缀的spring authorities,表示当前身份拥有的资源以及操作和数据范围等:

AR_{"resources":{"...":{"actions":[...]%2C"dataScopes":{...}}}}
或
SCOPE_AR_{"resources":{"...":{"actions":[...]%2C"dataScopes":{...}}}}

json 内对空格及英文逗号进行转义,分别为 %20、%2C。

移除前缀后,会将 JSON 转换为fly.core.security.authorization.authorized.AuthorizedResults类,完整属性可进该类查看,这里只做参考:

{
    "resources":{
        "news":{
            "actions":[
                "read"
            ],
            "dataScopes":{
                "query":{
                    "exprs":{
                        "scope1":[
                            "id in (select ...)",
                            "status = 1"
                        ]
                    },
                    "items":{
                        "id":[
                            "1",
                            "2"
                        ]
                    }
                }
            }
        }
    }
}

resources中每一个key表示资源路径,它可以是纯粹的一个名称,也可以是以引文句号.分隔的资源路径,还可以使用***通配符。:

示例:设有资源 resource1、test.resource1、test.resource2,每个资源只能匹配 resources 中一个资源权限结果。

  1. resource1;
  2. test.resource1;
  3. test.*;(能匹配 test.resource1、test.resource2,但优先级比 test.resource1 低)
  4. **;(能匹配所有资源,优先级最低)

# actions 操作

类似操作授权,首先需要将 API 定义为操作,对操作进行权限控制,不同的是可以不用在@ActionSecurity中配置scopes等校验条件。

使用@SecuredResource定义 Controller 下属于 news 资源:

@RestController
@RequestMapping("/news")
@SecuredResource("news") // 定义 Controller 下属于 news 资源
@ActionSecurity(actions = "query") // 表示需要对 query 操作进行权限控制
public class NewsController {
    
    @GetMapping("")
    @SecuredAction("query") // 定义为 query 操作
    public List<Object> query() {
        ...
    }
    
}

若当前身份拥有资源如下,拥有 news 资源且包含 query 操作或操作组,则权限校验通过,否则返回 403:

{
    "resources":{
        "news":{
            "actions":[
                "query"
            ]
        }
    }
}

# dataScopes 数据范围

类似行级权限控制,目前数据范围只对 query 列表查询操作生效。

涉及到数据访问,需要引入fly-orm模块。

  1. 在下列资源文件下,根据不同身份认证类型进行资源定义
  • classpath:authorization/client-pack.yml:纯客户端身份

  • classpath:authorization/user-pack.yml:含用户身份

authorities:
  # 表示 news 资源的 scope1 数据范围生效
  - resource: news
    dataScopes:
      - scope1
  1. Controller 类资源及操作定义
@RestController
@RequestMapping("/news")
@SecuredResource("news") // 定义 Controller 下属于 news 资源
@ActionSecurity(actions = "query") // 表示需要对 query 操作进行权限控制
public class NewsController {
    
    @Autowired
    protected Dao dao;
    
    @GetMapping("")
    @SecuredAction("query") // 定义为 query 操作
    public List<Object> query(CrudContext<T> context, Query query) {
        // crud 默认开启
        context.getQueryOperator().query(query, context.getEntityClass(), true);
        
        // dao 操作按需开启 enableDataSecurity()
        dao.createQuery(NewsEntity.class).enableDataSecurity().list();
        ...
    }
    
}
  • 表达式权限过滤

资源权限结果集:

{
    "resources":{
        "news":{
            "actions":[
                "read"
            ],
            "dataScopes":{
                "query":{
                    "exprs":{
                        "scope1":[
                            "id in (select ...)",
                            "status = 1"
                        ]
                    }
                }
            }
        }
    }
}

示例dataScopes表示在 query 操作中,当 scope1 数据范围生效时在查询中拼接表达式,最终列表查询 SQL 参考如下:

SELECT t.id, t.title FROM news t WHERE (其他查询条件) and ((id in (select ...)) or (status = 1)) LIMIT 20

数据范围中有多个表达式时,表达式之间为 or 关系。

  • 字段或关系 in values 权限过滤(数据范围命名需要为实体名或关系名)
authorities:
  - resource: news
    dataScopes:
      - id

资源权限结果集:

{
    "resources":{
        "news":{
            "actions":[
                "read"
            ],
            "dataScopes":{
                "query":{
                    "items":{
                        "id":[
                            "1",
                            "2"
                        ]
                    }
                }
            }
        }
    }
}

示例dataScopes表示在 query 操作中,当 id 数据范围生效时,若 id 为实体的字段,则在查询中拼接 in 条件进行权限过滤:

SELECT t.id, t.title FROM news t WHERE (其他查询条件) and (t.id in ('1','2')) LIMIT 20
  • 同时配置 exprs 或 items 可同时生效,但需注意此时数据范围自定义命名需要根据 items 规则要求,应为实体的字段名或关系名。

# 其他

# 允许匿名访问

允许API匿名访问可以使用@AllowAnonymous注解,处理逻辑为Spring SecuritypermitAll(),意味着当请求携带Authorization时,依然会进行验证有效性:

import fly.core.security.annotation.AllowAnonymous;

@RestController
@RequestMapping("/user")
@AllowAnonymous
public class UserController {
    
    @GetMapping("/{id}")
   	public Object find(@PathVariable String id) {
        ...
    }
    
    // 在允许匿名访问类中,单独设置该 API 不能匿名访问
    @PatchMapping("/{id}")
    @AllowAnonymous(false)
    public int update(@PathVariable String id) {
        ...
    }
    
}

# 忽略安全验证

可使用@Unsecured注解忽略部分API的安全验证,如同fly.security.ignored-urls配置,处理逻辑为Spring WebSecurityignoring(),注解使用示例如下:

import fly.core.security.annotation.Unsecured;

@RestController
@RequestMapping("/example")
@Unsecured
public class ExampleController {}

# 纯客户端身份

API默认需要携带用户身份访问。

  • 允许纯客户端身份访问

可通过fly.security.allow-client-only=true配置统一设置,还可使用@AllowClientOnly注解开启部分API的访问权限:

import fly.core.security.annotation.AllowClientOnly;

@RestController
@RequestMapping("/example")
@AllowClientOnly
public class ExampleController {}
  • 只允许纯客户端身份访问

可使用@ClientOnly注解,设置后无法通过纯用户身份或携带用户身份的请求访问该API

import fly.core.security.annotation.ClientOnly;

@RestController
@RequestMapping("/example")
@ClientOnly
public class ExampleController {}
顶部