在上一篇中,我们详细介绍了 认证 的流程。这一篇主要介绍授权流程,并介绍著名的RBAC权限模型。
说明: 本系列的文章使用的是 springBoot 2.7.11 (springboot 2x 最后一个稳定版本),对应的 Spring Security 是 5.7.8
授权管理器 AuthorizationManager
先让我们回顾下在认证阶段的一些事情,从数据库查询用户信息后组装 UserDetail时,有一个关于权限的方法
public Collection<? extends GrantedAuthority> getAuthorities();
也就是说在认证阶段,我们把用户的账号、密码和权限信息一并获取成功,然后存入未认证对象 Authentication 进行认证和授权。
打开Authentication这个类 ,我们可以清晰地看到这个类是有获取权限这个方法的,证明我们理解是对的。
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
}
一个用户能正常访问资源,需要经过认证和授权两个流程。认证是为了识别这个用户的账号、密码是否正确,授权是为了检查已认证用户是否具有权限访问该资源(比如接口资源)。
Spring Security是基于 授权管理器 AuthorizationManager来完成授权流程的。
Spring Security 核心
回顾下Spring Security 的核心是基于拦截器来实现的,其中有一个授权拦截器 AuthorizationFilter ,这个拦截器就会调用 授权管理器 AuthorizationManager 来进行授权动作。
其中AuthorizationManager有如下实现类
我们可以通过自定义 AuthorizationManager来完成自己特殊的授权流程。
但一般情况下,我们是不改动现有的授权管理器,而是把重心放在 用户角色权限设计和管理上.
开启资源鉴权
- 开启资源授权配置
在 Spring Security的配置类上,使用@EnableGlobalMethodSecurity开启资源授权。比如
@Configuration
@EnableWebSecurity
@Slf4j
// 开启授权限制
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
//...
}
在上面的时候,我们有提到授权管理器 AuthorizationManager 会根据不同的配置调用不同的实现类进行授权动作,这个配置就是在这里。
public @interface EnableGlobalMethodSecurity {
boolean prePostEnabled() default false;
boolean securedEnabled() default false;
boolean jsr250Enabled() default false;
}
可以看到,有3种不同的配置。
- 资源设置权限
在上面我们配置了@EnableGlobalMethodSecurity(prePostEnabled = true),在给资源配置权限的时候也使用配套的@PreAuthorize
@RequestMapping("/tellAuthority")
@PreAuthorize("hasAuthority('test')")
public Result<String> tellAuthority() {
return Result.success("tellAuthority,需要用户具有 test权限才可以访问");
}
- 如何给用户设置权限
回到之前的UserDetails可以看到,权限就是GrantedAuthority对象。
public Collection<? extends GrantedAuthority> getAuthorities();
我们可以使用它的一个通用实现 SimpleGrantedAuthority 即可。
查询权限
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
User user = userMapper.getUser(username);
// if (Objects.isNull(user)) {
// //抛出异常之后,后续拦截器会继续处理
// throw new BizException("用户名或密码错误");
// }
//todo 查询对应的权限信息
List<String> list = new ArrayList<>(Arrays.asList("test", "admin"));
LoginUser loginUser = new LoginUser(user, list);
return loginUser;
}
}
权限设置到UserDetail
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions;
// 该字段不需要序列化
@JSONField(serialize = false)
private List<GrantedAuthority> grantedAuthorities;
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (grantedAuthorities != null) {
return null;
}
grantedAuthorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return grantedAuthorities;
}
//...
}
RBAC权限模型
上面,我们提到了有用户权限,那么到底该如何做数据库设计呢?
这里我们引入著名的RBAC权限模型。
RBAC权限模型就是基于五张关键表来实现的。
首先,我们有用户表来存储账号密码信息。
其次,我们有权限表,记录各种权限。
第三,在权限系统中有角色这个概念。资源可以分配给具有某项权限的人,也可以分配给具有某个角色的人。
第四,一个人可能有多个角色,所以需要一张表把人与角色关联起来。人对角色,多对多。
第五,一个角色也可能有多个权限,所以需要一张表把角色与权限管理起来。角色与权限多对多。
- 五张关键表
- 用户表 , 存储账号密码信息
- 角色表 , 存储角色信息
- 权限表,存储权限信息(权限code 和 中文名称,名称是直接展示给人看的)
- 角色与权限关联表,存储角色与权限的映射关系
- 人与角色关联表,存储人与角色的映射关系
RBAC权限模型
一个最基础的RBAC权限模型的表设计就完成了,然后可以根据具体的业务需求添加相关字段即可。