# API 权限
针对业务场景提供API级别、操作分组以及资源类型进行权限控制。
# 使用基础
# scope
scope来源于当前请求携带的身份认证信息。在Spring Security里,对应Authentication中以SCOPE_为前缀的authorities。
# role
role与scope相似,也是来源于当前请求携带的身份认证信息。在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 |
# 授权
# 简单授权
若只需要根据scope或permission来控制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 | admin、write |
@DeleteAction | delete | admin |
@UpdateAction | update | admin、write |
@FindAction | find | admin、read |
@QueryAction | query | admin、read |
上列常用操作已在Crud接口中使用,如@QueryAction在CrudQuery接口中已应用:
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 中一个资源权限结果。
- resource1;
- test.resource1;
- test.*;(能匹配 test.resource1、test.resource2,但优先级比 test.resource1 低)
- **;(能匹配所有资源,优先级最低)
# 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模块。
- 在下列资源文件下,根据不同身份认证类型进行资源定义
classpath:authorization/client-pack.yml:纯客户端身份classpath:authorization/user-pack.yml:含用户身份
authorities:
# 表示 news 资源的 scope1 数据范围生效
- resource: news
dataScopes:
- scope1
- 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 Security的permitAll(),意味着当请求携带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 WebSecurity的ignoring(),注解使用示例如下:
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 {}