一、思路设计整合
我们之前已经为大家展示了一下shiro授权数据在Realm中的简单实现,那这时我们就要将shiro的授权数据持久化至数据库中去,因为我们将来一个系统一定是有很多用户,并且也是拥有很多角色的,我们的授权数据不可能总是在Realm中写,所以这时,我们就要将授权数据持久化进数据库中去。接下来就简单分析一下思路。
首先,我们要实现从数据库中获取权限信息,那么第一件事就是要设计表结构,我们有这样几种设计思路:
第一种模式: 用户 ----------> 角色 ----------> 权限 ---------> 资源url
第二种模式:用户 -----------> 角色
第三种模式:用户 -----------> 权限
这里我们就以第一种模式来实现权限控制,首先是权限控制的思维导图:
这里我们的思路就是,一个用户可以拥有多个角色,那反过来一个角色也可以被多个用户拥有,同样的一个角色可以有多个权限,那一个权限也会被多个角色持有,也就是说用户和角色之间的关系是多对多的,角色和权限也是多对多的,为了将来我们从数据库取用户的权限(也就是说要将多对多处理为两个一对多),我们还需要两张用户和角色之间的中间关系表,也需要角色和权限字符串之间的角色权限表。所以说其实在权限的数据库表设计方面,我们一定是要站在多个角度来设计数据库表的。
二、数据库表设计
1.表设计
那么我们就开始设计表了,首先是角色表:
表名:t_role
id,角色名称
接着是权限表:
表名:t_perms
id,权限名称
然后再来创建用户与角色的中间表:
表名:t_user_role
再之后是角色权限的中间表:
表名:t_role_perms
五张表总览:
2.表中角色初始化
好了,那么我们接下来为角色表:t_role 初始化几个角色信息:
这里我们设计一共三种角色,第一个是admin,是最大的权限,然后分别再设计两个user和product角色。
然后我们再来填充t_user和t_role两张表的中间表t_user_role:
id是中间表自己的id,user_id是user表中的id,role_id是role表中的id。
由上面的中间表可以看出来,一号用户拥有一号权限,二号用户拥有二号和三号权限
我们再回头来看一下t_user表中的数据;
通过表中的数据可以看出来,日后我们如果根据admin用户查的话他是一号权限,也就是admin权限,如果根据zhangsan查的话就是二号和三号权限。
数据和表都搞好之后,我们就可以来搞一下代码了:
三、代码中的实现
首先是实体类:
权限字符串:Perms:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Perms {
private String id;
private String name;
}
角色Role:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Role {
private String id;
private String name;
}
在User实体类中我们再定义一个角色的集合:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User implements Serializable {
private String id;
private String username;
private String password;
private String salt;
//定义角色集合
private List<Role> roles;
}
在DAO接口中再新定义一个方法(findRolesByUsername):
@Mapper
public interface UserDao {
/**
* 用户注册接口
*
* @param user
*/
void save(User user);
/**
* 根据用户名查询用户信息
*
* @param username
* @return
*/
User findByUsername(String username);
/**
* 根据用户名查询角色信息
* @param username
* @return
*/
User findRolesByUsername(String username);
}
对应DAO层的方法在XML中写SQL语句(findRolesByUsername):
<resultMap id="userMap" type="com.csdn.springboot_shiro_jsp_demo_1.login.pojo.User">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<!--角色信息-->
<collection property="roles" javaType="list" ofType="com.csdn.springboot_shiro_jsp_demo_1.login.pojo.Role">
<id column="id" property="id"/>
<result column="rolename" property="name"/>
</collection>
</resultMap>
<select id="findRolesByUsername" parameterType="String" resultMap="userMap">
SELECT u.id uid,u.username,r.id,r.rolename FROM t_user u
LEFT JOIN t_user_role ur
ON u.id = ur.user_id
LEFT JOIN t_role r
ON r.id = ur.role_id
WHERE u.username = #{username}
</select>
接着是业务层Service:
/**
* 根据用户名查询角色信息
* @param username
* @return
*/
User findRolesByUsername(String username);
业务层实现类Impl:
/**
* 根据用户名查询角色信息
* @param username
* @return
*/
@Override
public User findRolesByUsername(String username) {
User rolesByUsername = userDao.findRolesByUsername(username);
return rolesByUsername;
}
业务层写完之后,我们就要来自定义Realm来完成授权的实现:
/**
* 自定义Realm
*/
public class MyRealm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//首先拿到用户的主身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
//用获取容器中对象的工具类拿到userService对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
//拿到一个用户对象
User user = userService.findRolesByUsername(primaryPrincipal);
//使用CollectionUtils方法判空,如果roles集合非空说明有角色信息
if (!CollectionUtils.isEmpty(user.getRoles())){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//遍历角色集合之后添加角色字符串
user.getRoles().forEach(role -> simpleAuthorizationInfo.addRole(role.getName()));
//返回授权信息
return simpleAuthorizationInfo;
}
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//拿到用户名
String principal = (String) authenticationToken.getPrincipal();
//用获取容器中对象的工具类拿到userService对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
//调用方法拿到user对象
User user = userService.findByUsername(principal);
//如果user对象不为空
if (!StringUtils.isEmpty(user)){
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
来看一下这段代码,上面的方法就是授权的实现,那么我们来分析一下,同样的,我们再授权的方法中也需要去用到我们在工厂中的业务对象,所以首先是拿到工厂的业务对象,然后拿业务对象去调用我们的业务层的方法,拿到角色信息,拿到这个角色信息集合之后,首先要判断这个集合不为空,表明是有角色信息,然后我们用Java8的新特性Stream流来遍历这个集合并且在遍历的时候拿到角色信息之后添加角色信息。
好了这一步也完成了之后,为了避免大家忘记我们的页面角色是怎么定义的,我再将页面粘贴出来:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>后台管理系统</title>
</head>
<body>
<h1>系统主页V1.0</h1>
<ul>
<shiro:hasRole name="admin">
<li>物流管理</li>
<ul>
<li><a href="">发货时间</a></li>
<li><a href="">预计送达</a></li>
<li><a href="">承运单位</a></li>
</ul>
<li>订单管理</li>
<ul>
<li><a href="">订单新增</a></li>
<li><a href="">订单删除</a></li>
<li><a href="">订单修改</a></li>
</ul>
<li>商品管理</li>
<ul>
<li><a href="">商品新增</a></li>
<li><a href="">商品删除</a></li>
<li><a href="">商品修改</a></li>
</ul>
</shiro:hasRole>
<shiro:hasAnyRoles name="user,admin">
<li>用户管理</li>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">用户新增</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">用户删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">用户修改</a></li>
</shiro:hasPermission>
</ul>
</shiro:hasAnyRoles>
</ul>
<a href="${pageContext.request.contextPath}/user/logout">用户登出</a>
</body>
</html>
可以看到我们的页面是admin和user权限是可以看到用户管理,只有admin能看到其他模块。然后我们再来看一下数据库我们的角色和用户的对应关系:
首先我们来看一下zhangsan用户的角色信息:
可以看到zhangsan拥有的权限为user和product角色
然后是admin用户的角色信息:
通过关联查询我们可以看到,zhangsan的角色信息是user和product,admin的角色信息为admin
然后我们来测试一下:
ok首先是admin登录,它的角色也是admin:
好的,然后是zhangsan用户登录:
好的,和我们预想的效果一样,这就是我们预期的,从数据库中获取角色信息的简单实现。