Sping Security (三): 授权流程

180 阅读4分钟

在上一篇中,我们详细介绍了 认证 的流程。这一篇主要介绍授权流程,并介绍著名的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来完成授权流程的。

image.png

Spring Security 核心

回顾下Spring Security 的核心是基于拦截器来实现的,其中有一个授权拦截器 AuthorizationFilter ,这个拦截器就会调用 授权管理器 AuthorizationManager 来进行授权动作。

image.png

其中AuthorizationManager有如下实现类

image.png

我们可以通过自定义 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 和 中文名称,名称是直接展示给人看的)
    • 角色与权限关联表,存储角色与权限的映射关系
    • 人与角色关联表,存储人与角色的映射关系

image.png

RBAC权限模型

一个最基础的RBAC权限模型的表设计就完成了,然后可以根据具体的业务需求添加相关字段即可。