我们开发一个系统,必然面临权限控制的问题,不同的用户具有不同的访问、操作、数据权限。形成理论的权限控制模型有:自主访问控制(DAC: Discretionary Access Control)、强制访问控制(MAC: Mandatory Access Control)、基于属性的权限验证(ABAC: Attribute-Based Access
Control)等。最常被开发者使用也是相对易用、通用的就是RBAC权限模型(Role-Based Access
Control)
1、RBAC权限模型简介
RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。模型中有几个关键的术语:
- 用户:系统接口及访问的操作者
- 权限:能够访问某接口或者做某操作的授权资格
- 角色:具有一类相同操作权限的总称
RBAC权限模型核心授权逻辑如下:
- 某用户是什么角色?
- 某角色具有什么权限?
- 通过角色对应的权限推导出用户的权限
2、RBAC的演化进程
2.1、用户与权限直接关联
想到权限控制,人们最先想到的一定是用户与权限直接关联的模式,简单地说就是:某个用户具有某些权限。如图:
- 张三具有所有权限他可能是一个超级管理员.
- 李四,王五 具有添加商品和审核商品的权限有可能是一个普通业务员
这种模型能够清晰的表达用户与权限之间的关系,足够简单。但同时也存在问题:
- 现在用户是张三、李四,王五以后随着人员增加,每一个用户都需要重新授权
- 操作人员的他的权限发生变更后,需要对每个一个用户重新授予新的权限
2.2、用户与角色关联
这样只需要维护角色和权限之间的关系就可以了. 如果业务员的权限发生变更, 只需要变动业务员角色和权限之前的关系进行维护就可以了. 用户和权限就分离开来了. 如下图
3、基于RBAC设计权限表结构
- 一个用户有一个或多个角色
- 一个角色包含多个用户
- 一个角色有多种权限
- 一个权限属于多个角色
4、基于Spring Security 实现RBAC权限管理
4.1、动态查询数据库中用户对应的权限
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lagou.domain.Permission;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface PermissionMapper extends BaseMapper<Permission> {
/**
* 根据用户ID查询权限
*
* @param id
* @return
*/
@Select("SELECT p.* FROM t_permission p,t_role_permission rp,t_role r,t_user_role ur,t_user u " +
"WHERE p.id = rp.PID AND rp.RID = r.id AND r.id = ur.RID AND ur.UID = u.id AND u.id =#{id}")
List<Permission> findByUserId(Integer id);
}
4.2、给登录用户授权
这个是在实现UserDetailsService接口,在重写的loadUserByUsername的方法中
Collection<GrantedAuthority> authorities = new ArrayList<>(); // 权限的集合,先声明一个权限集合, 因为构造方法里面不能传入null
// 基于数据库查询用户对应的权限
List<Permission> permissionList = permissionService.findByUserId(user.getId());
for (Permission permission : permissionList) {
// 授权
authorities.add(new SimpleGrantedAuthority(permission.getPermissionTag()));
}
4.3、设置访问权限
这个是在我们自定义类SecurityConfig继承了WebSecurityConfigurerAdapter接口,在其重写方法中设置,void configure(HttpSecurity http)
// 查询数据库所有权限列表
List<Permission> list = permissionService.list();
for (Permission permission : list) {
http.authorizeRequests().antMatchers(permission.getPermissionUrl())
// 添加请求权限
.hasAuthority(permission.getPermissionTag());
}
之所以使用hasAuthority而不是使用hasRole是因为hasRole会在权限的前面加上"ROLE_".
5、基于页面端标签的权限控制
在jsp页面或者thymeleaf模板页面中我们可以使用spring security提供的权限标签来进行权限控制.要想使用thymeleaf为SpringSecurity提供的标签属性,首先需要引入thymeleaf-extras-springsecurity依赖支持。
5.1、在pom 文件中的引入springsecurity的标签依赖thymeleaf-extras-springsecurity5。
<!--添加thymeleaf为SpringSecurity提供的标签依赖 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
5.2、在html文件里面申明使用
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
5.3、常用SpringSecurity的标签属性介绍
判断用户是否已经登陆认证,引号内的参数必须是isAuthenticated()。
sec:authorize="isAuthenticated()"获得当前用户的用户名,引号内的参数必须是name。
sec:authentication=“name”
判断当前用户是否拥有指定的权限。引号内的参数为权限的名称。
sec:authorize=“hasRole(‘role’)”
5.4、SpringSecurity标签的使用
<div class="leftnav">
<div class="leftnav-title">
<div sec:authorize="isAuthenticated()"> <!--有权限时,为true,则会显示-->
<span sec:authentication="name"></span>
<img src="images/y.jpg" class="radius-circle rotate-hover" height="50" alt=""/>
</div>
</div>
<div sec:authorize="hasAuthority('user:findAll')"> <!--有权限时,为true,则会显示-->
<h2><span class="icon-user"></span>系统管理</h2>
<ul style="display:block">
<li><a href="/user/findAll" target="right"><span class="icon-caretright"></span>用户管理</a></li>
<li><a href="javascript:void(0)" onclick="toCors()" target="right">
<span class="icon-caret-right"></span>跨域测试</a></li>
</ul>
</div>
<div sec:authorize="hasAuthority('product:findAll')"> <!--有权限时,为true,则会显示-->
<h2><span class="icon-pencil-square-o"></span>数据管理</h2>
<ul>
<li><a href="/product/findAll" target="right"><span class="iconcaret-right"></span>商品管理</a></li>
</ul>
</div>
</div>
6、记录bug
当我们开启CSRF防护后,我们点击项目中的登录按钮,会报404错误,这是因为logout登出功能的方法必须为post请求,并且我们还要携带
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>这里面的参数。这样才能登出成功,而不会报错。