权限系统的必要性
权限管理系统是众多大型项目的标配。可以说,如果没有权限系统的支持,很多功能都会变得难以实现。
OA 系统为例,各个部门要有不同的操作界面、资料。比如,人事部只要公司的人员资料录入,销售部只要公司的销售资料录入。这时候,如果没有限制各个部门的权限,系统就很容易出现:人员误操作、系统的学习成本高昂等情况。
总结上述例子,权限系统有以下优点:
- 安全性:避免误操作、人为破坏、数据泄露等;
- 数据隔离:不同权限能看到、操作不同的数据;
- 明确职责:可以分配:运营、客服等不同角色,领导及下属等不同等级;
权限系统分类
用户—权限
适合人员少,功能固定,或者特别简单的系统
例如,Mysql中用户对数据库的操作权限
RBAC(用户->角色->权限)
用户->角色->权限,都适用。整体关系,如下图:
![](https://img.haomeiwen.com/i13492280/8e139d253e0d1e4b.png)
文章中,我们用 shiro 来实现 RBAC 权限系统,下面我们先搭建一个基本的业务系统。
业务系统
创建数据表
shiro 是一个 RBAC 型的权限系统,适用于:用户->角色->权限。所以,我们需要新建的数据表有:
- 用户表,包括:id、用户名、密码;
- 角色表,包括:id、英文名、中文名;
- 权限表,包括:id、权限标识、对应url;
- 用户、角色关联表,包括:用户id、角色id;
- 角色、权限关联表,包括:角色id、权限id;
SQL脚本如下:
init.sql
实体类
根据数据库中的表,我们可以得出下面 3 个实体类,他们的关系:用户 ——> 角色 ——> 权限,如 UML 图:
他们的代码,即下列所示:
- 用户对象
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class User implements Serializable {
// 唯一编号
private Integer uid;
// 用户名
private String username;
// 密码
private String password;
// 角色列表
private Set<Role> roles = new HashSet<>();
}
- 角色对象
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class Role implements Serializable {
// 唯一编号
private Integer rid;
// 角色名称
private String rname;
// 角色包含的权限集合
private Set<Permission> permissions = new HashSet<>();
/** 省略 getter 和 setter 方法 **/
}
- 权限对象
import java.io.Serializable;
public class Permission implements Serializable {
// 唯一编号
private Integer pid;
// 权限标识
private String name;
// 权限对应的url
private String url;
/** 省略 getter 和 setter 方法 **/
}
数据库查询代码
程序使用 SpringBoot + Mybatis,这里我们只关注 XML 中的主要 SQL,其它请参考下载项目代码:
<!-- 拼装返回结果,用于映射 User 对象 -->
<resultMap id="userMap" type="com.mmall.demo2.model.User">
<id property="uid" column="uid" />
<result property="username" column="username" />
<result property="password" column="password" />
<!-- 拼装角色列表 -->
<collection property="roles" ofType="com.mmall.demo2.model.Role">
<id property="rid" column="rid" />
<result property="rname" column="rname" />
<!-- 拼装权限列表 -->
<collection property="permissions" ofType="com.mmall.demo2.model.Permission">
<id property="pid" column="pid" />
<result property="name" column="name"/>
<result property="url" column="url" />
</collection>
</collection>
</resultMap>
<!-- 根据用户名查询用户、角色、权限 -->
<select id="findByUsername" parameterType="string" resultMap="userMap">
SELECT u.*, r.*, p.*
FROM user u
INNER JOIN user_role ur on ur.uid = u.uid
INNER JOIN role r on r.rid = ur.rid
INNER JOIN permission_role pr on pr.rid = r.rid
INNER JOIN permission p on pr.pid = p.pid
WHERE u.username = #{username}
</select>
控制层
项目使用 SpringMVC 提供“视图及数据交互”接口,属于主流技术,这里不再详细说明,直接贴出整个 Controller :
import com.mmall.demo2.model.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
/**
* 登陆页面
* @return
*/
@RequestMapping("/login")
public String login() {
return "login";
}
/**
* 主页
* @param model
* @return
*/
@RequestMapping("/index")
public String index(Model model) {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
model.addAttribute("user", user);
return "index";
}
/**
* 用户登陆逻辑
* @param username
* @param password
* @return
*/
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username,
@RequestParam("password") String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return "redirect:/index";
} catch (Exception e) {
return "login";
}
}
/**
* 用户注销逻辑
* @return
*/
@RequestMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
if (subject != null) {
subject.logout();
}
return "login";
}
/**
* 无权限访问页面
* @return
*/
@RequestMapping("unauthorized")
public String unauthorized() {
return "unauthorized";
}
/**
* 管理员专属页面,只有管理员角色可以访问
* @return
*/
@ResponseBody
@RequestMapping("/admin")
public String admin() {
return "admin";
}
/**
* 拥有“修改”权限,才能访问该接口
* @return
*/
@ResponseBody
@RequestMapping("/edit")
public String edit() {
return "edit success";
}
/**
* 拥有“查询”权限,才能访问该接口
* @return
*/
@ResponseBody
@RequestMapping("/query")
public String query() {
return "query success";
}
}
写在最后
我们已经完成了权限系统的基础搭建。下篇文章,我们就开始把 Shiro 整合到项目里面。这里附上项目源码:
网友评论