Shiro学习(八)--使用数据库进行授权管理

一般的,企业的用户数据不可能直接写在文件中,我们需要设计数据库用来存储这些重要的数据,假设现在有权限(permission),角色(role),用户(user)三个重要的主题,那么如何将这三者联系起来呢?我们看下图:
在这里插入图片描述
图中告诉我们:
permissionrole的关系是多对多的关系
roleuser的关系也是多对多的关系

一、数据库表构建

其中permission、role、tb_user、permission_role、role_user

  • tb_user用户表
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`  (
  `userId` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `nikeName` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '无名氏',
  `name` char(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `email` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `photo` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '',
  `sex` char(3) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0',
  `detail` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '此人比较懒,没有任何描述',
  `previledge` int(2) NULL DEFAULT 0,
  PRIMARY KEY (`userId`) USING BTREE,
  UNIQUE INDEX `username`(`username`) USING BTREE,
  UNIQUE INDEX `username_password_index`(`username`, `password`) USING BTREE,
  UNIQUE INDEX `email`(`email`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  • role角色表
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `role_id` int(11) NOT NULL,
  `role_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `sn` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '管理员', 'Manager');
INSERT INTO `role` VALUES (2, '学生', 'Student');
INSERT INTO `role` VALUES (3, '作者', 'Author');
  • permission权限表
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `permission_id` int(11) NOT NULL,
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '资源表达式,资源名:资源操作:资源实例',
  PRIMARY KEY (`permission_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  • role_permission角色权限表
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `role_id` int(40) NULL DEFAULT NULL,
  `permission_id` int(40) NULL DEFAULT NULL,
  INDEX `role_id`(`role_id`) USING BTREE,
  INDEX `permission_id`(`permission_id`) USING BTREE,
  CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `role_permission_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`permission_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

  • user_role用户角色表
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `user_id` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `role_id` int(11) NOT NULL,
  INDEX `user_id`(`user_id`) USING BTREE,
  INDEX `role_id`(`role_id`) USING BTREE,
  CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`userId`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

二、类的编码
2.1、有了上面几张表后,我们需要建立对应的bean
  • UserInfo映射到tb_user表 存放用户信息
    /**
     * 用户信息
     */
    public class UserInfo {
        //用户编号(唯一标志符)
        private String userId;
        //用户名
        @Size(min = 3,max = 40,message = "用户名长度请保持在{min}到{max}之间")
        private String username;
        //账户密码
        @NotBlank(message = "密码不得为空!" )
        private String password;
        //昵称
        private String nikeName;
        //姓名
        private String name;
        //邮箱
        private String email;
        //头像
        private String photo;
        //个人描述
        private String detail;
        //性别 0是男,1是女
        private String sex;
  • Role映射到role表
public class Role {

	//角色id
    private int roleId;
	//角色名称(中文)
    private String roleName;
	//角色code(英文)
    private String sn;
    //一个角色role存在多个资源映射关系
    private List<RolePermission> rolePermissions;
  • Permission映射到permission表
public class Permission {
	//权限id
    private int permissionId;
	//权限名称
    private String name;
	//shiro的权限资源表达式,比如 user:create,user:delete 
    private String resource;
  • 中间表:映射tb_user拥有的不同的role: user_role

public class UserRole {
	//用户uid
    private String userId;
    //用户对应的角色列表
    private List<Role> roles;
  • 中间表:映射role拥有的不同的Permission:role_permission
public class RolePermission {
	//角色id
    private int roleId;
    //角色对应的资源列表
    private List<Permission> permissionList;
2.3. 使用mybatis编写他们的Mapper文件
  • 管理User的数据库操作的Dao接口
    public interface ShiroUserDao {
    
        //根据用户输入的用户名查询用户信息
        public UserInfo searchUserByUsername(String username);
    
    }

对应的ShiroUserMapper文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.onlineStudySystem.dao.shiroDao.ShiroUserDao">
    
        <resultMap id="userInfoMap" type="UserInfo">
            <id property="userId" column="userId"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="nikeName" property="nikeName"></result>
            <result column="name" property="name"></result>
            <result column="email" property="email"></result>
            <result column="photo" property="photo"></result>
            <result column="detail" property="detail"></result>
            <result column="sex" property="sex"></result>
        </resultMap>
    
        <select id="searchUserByUsername" resultMap="userInfoMap" parameterType="java.lang.String">
            select * from tb_user where username = #{username,jdbcType=VARCHAR}
        </select>
    
    </mapper>

管理Role获取用户角色以及角色对应的资源的接口

public interface RoleDao {

    //查询所有的角色
    public List<Role> searchAllRole();

    //根据用户传进的角色id查询指定角色
    public List<Role> searchByRoleId(int roleId);

    //根据角色id查询资源角色映射表
    public List<RolePermission> searchPermissionIdByRoleId(int roleId);

    //根据用户id查询角色
    public List<UserRole> searchRoleByUserId(String userId);

}

对应的ShiroRoleMapper文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.onlineStudySystem.dao.shiroDao.RoleDao">
    
        <resultMap id="roleMap" type="com.onlineStudySystem.bean.shiro.Role">
            <id property="roleId" column="role_id"></id>
            <result property="roleName" column="role_name" jdbcType="VARCHAR"></result>
            <result property="sn" column="sn" jdbcType="VARCHAR"></result>
            <collection fetchType="lazy" property="rolePermissions" column="role_id" select="com.onlineStudySystem.dao.shiroDao.RoleDao.searchPermissionIdByRoleId"></collection>
        </resultMap>
    
        <resultMap id="userRoleMap" type="com.onlineStudySystem.bean.shiro.UserRole">
            <result column="user_id" property="userId"></result>
            <collection property="roles" column="role_id" select="searchByRoleId"></collection>
        </resultMap>
    
        <resultMap id="rolePermissionMap" type="com.onlineStudySystem.bean.shiro.RolePermission">
            <result property="roleId" column="role_id"></result>
            <collection fetchType="lazy" property="permissionList" column="permission_id" select="com.onlineStudySystem.dao.shiroDao.ShiroPermissionDao.searchPermissionById">
            </collection>
        </resultMap>
    
        <select id="searchAllRole" resultMap="roleMap">
            select * from role
        </select>
    
        <select id="searchByRoleId" resultMap="roleMap">
            select * from role where role_id = #{role_id}
        </select>
    
        <select id="searchPermissionIdByRoleId" resultMap="rolePermissionMap">
            SELECT * from role_permission where role_id =#{roleId}
        </select>
    
        <select id="searchRoleByUserId" resultMap="userRoleMap">
            select * from user_role where user_id = #{userId}
        </select>
    
    </mapper>
  • 用于获取对资源权限(Permission)表达式的接口
    public interface ShiroPermissionDao {
    
        //根据PermissionId查询某条Permission
        public Permission searchPermissionById(int permissionId);
    
        //填充permission数据
        public int insertPermissionInfo(@Param("permissions") List<Permission> permissions);
    
        //搜索现有的所有权限
        public List<Permission> searchAllPermission();
    }

对应的Mapper文件

    <mapper namespace="com.onlineStudySystem.dao.shiroDao.ShiroPermissionDao">
    
    
        <resultMap id="resMap" type="com.onlineStudySystem.bean.shiro.Permission">
            <id property="permissionId" column="permission_id"></id>
            <result property="name" column="name"></result>
            <result property="resource" column="resource"></result>
        </resultMap>
    
        <select id="searchPermissionById" resultMap="resMap">
            select * from permission where permission_id = #{permissionId}
        </select>
    
        <insert id="insertPermissionInfo" parameterType="com.onlineStudySystem.bean.shiro.Permission">
           insert into permission values
           <foreach collection="permissions"  item="permission" open="(" close=")" separator=",">
            #{permission.permissionId},#{permission.name},#{permission.resource}
           </foreach>
        </insert>
    
        <select id="searchAllPermission" resultMap="resMap">
            select * from permission
        </select>
    </mapper>
2.4 在mybatis-config全局配置文件中开启懒加载模式
    <configuration>
        <settings>
            <setting name="cacheEnabled" value="true"/>
            <!-- 打开延迟加载的开关 -->
             <setting name="lazyLoadingEnabled" value="true" />
                <!-- 将积极加载改为消息加载即按需加载 -->
             <setting name="aggressiveLazyLoading" value="false" />
    <!--        目的是排除不相干的方法导致立刻加载的情况-->
            <setting name="lazyLoadTriggerMethods" value="toString()"/>
        </settings>
    </configuration>

这个Mapper文件中,我使用了懒加载模式,如果你懒加载的对象中,有toString方法的话,一定要加上
<setting name="lazyLoadTriggerMethods" value="toString()"/>

2.5 小总结:

上面这几个Mapper文件中的关联关系图如下:
在这里插入图片描述

三、加载权限表达式

我们在创建完表和mapper文件之后,要求进行保存权限的表达式以及对应的名称,也就是向Permission表中填充数据。我们就从Controller接口介绍,当我们请求uri重新加载数据,比如/reload ,这个接口将会去扫描所有的controller的requestMapping方法,然后检查是否标注@RequiresPermissions注解,如果标注了,则取出注解内的值,去数据库查看是否存在这个资源,如果不存在,则自动的插入资源到数据中,注意这里还提到了一个注解@PermissionsName,它描述资源的中文含义,应该要和@RequiresPermissions注解共同出现,标注一个权限。 /reload接口能够帮我们动态的实现将不存在的资源重新加载到db中看下面的具体代码:

@Controller
public class PermissionContrller {

    //请求映射处理器
    //springMvc在启动的时候将所有贴有请求映射标签;RequestMapper方法收集起来看封装到该对象中。
    @Autowired
    RequestMappingHandlerMapping rmhm;
    @Autowired
    PermissionService permissionService;

    @RequestMapping("/reload")
    @ResponseBody
    public String reload() throws Exception{
        //获取所有的controller中请求方法
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = rmhm.getHandlerMethods();
        //请求方法集合
        Collection<HandlerMethod> values = handlerMethods.values();
        //在开始重新刷新所有的权限集合之前,我们查询数据库并记录数据库中已有的权限
        List<Permission> permissions = permissionService.searchAllPermission();
        HashSet<String> set = new HashSet<>();
        for(Permission p : permissions){
            set.add(p.getResource());
        }

        //存放数据库没有的权限
        List<Permission> noExistPermList = new ArrayList<>();
        //通过请求方法中,遍历标签
        for(HandlerMethod method : values){
            if(method!=null){
                RequiresPermissions methodAnnotation = method.getMethodAnnotation(RequiresPermissions.class);
                //获取权限
                String resource = methodAnnotation.value()[0];
                //查看权限值是否不为空
                if(!StringUtils.isEmpty(resource)){
                    System.out.println(resource);
                    //获取资源名字
                    String name = method.getMethodAnnotation(PermissionsName.class).value();
                    System.out.println(name);
                    //检查资源是否存在数据库中
                    if(!set.contains(resource)) {
                        //记录到一个数组,加载到数据库
                        Permission p = new Permission();
                        p.setName(name);
                        p.setResource(resource);
                        noExistPermList.add(p);
                    }
                }
            }
        }
        //        更新数据库
        int res = permissionService.insertPermissionInfo(noExistPermList);
        return res>0?"更新成功":"更新失败";
    }
}

对上面reload接口的代码再进一步讲解:

  1. RequestMappingHandlerMapping 可以获取所有Controller中的方法。通过这些方法,我们可以拿到他们的元信息,比如方法标注的注解,以及注解的值等。
    2.RequiresPermissions这是一个注解类,它的使用方式如下
  @RequestMapping("/shiro/deletecomment")
    //必须要求拥有comment:up权限才可以进行提交comment
    @RequiresPermissions("comment:delete")
    @PermissionsName("删除评论")
    @ResponseBody
    public String deleteComment(){
        return "删除评论成功!";
    }

在shiro环境中,用于标注用户必须要具备什么样的权限才可以访问目标方法,在方法被调前进行判断。

  1. PermissionsName 这也是一个注解类,不过这是我们自定义的,为权限操作起一个中文名称,可有可无。配合@RequirePermissions注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionsName {
    String value();
}

整体的逻辑

首先进来先获取所有@Controller下的方法(标注@xxxMapping的) 。依次遍历得到的所有方法,获取标注RequirePermissions注解的方法。然后再获取@RequirePermissions注解中设置的值(values),它返回一个数组,因为我们只设置一个权限值,故此只用取数据的第一个即可。然后获取我们自定义的PermissionsName中的value值。将他们和之前数据库存有的权限依次比较,如果发现不存在于数据库中就将他们进行添加到数据库即可。

四、自定义Realm,进行数据库方式授权和登陆认证

在我们自定义的reaml中,对通过登录认证的不同用户进行权限的查询和分配。
代码:

import com.onlineStudySystem.bean.UserInfo;
import com.onlineStudySystem.bean.shiro.Permission;
import com.onlineStudySystem.bean.shiro.Role;
import com.onlineStudySystem.bean.shiro.RolePermission;
import com.onlineStudySystem.bean.shiro.UserRole;
import com.onlineStudySystem.service.shiro.RoleService;
import com.onlineStudySystem.service.shiro.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;


/**
 * 主要根据用户参数去数据库获取对应的角色、资源、账号信息,并封装返回给Authorticator调度判断。
 */
public class UserReaml extends AuthorizingRealm {

    @Autowired
    RoleService roleService;

   @Autowired
   UserService userService;

    @Override
    public String getName() {
        return "UserReaml";
    }

    /**
     * 获取权限信息
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username =(String) principals.getPrimaryPrincipal();
        //获取到username之后,我们对它进行查看权限
        UserInfo userInfo = userService.searchUserByUsername(username);
        //获取用户对应角色信息
        List<UserRole> userRoles = roleService.searchRoleByUserId(userInfo.getUserId());
        List<String> permissionList = new ArrayList<>();
        List<String> roles = new ArrayList<>();
        //遍历并查看它所拥有的全部角色,注意这里获取的是用户和角色映射表存放的信息
        for(UserRole user_role : userRoles){
            //抽取出所有的角色列表
            List<Role> roleList = user_role.getRole();
            for(Role role : roleList){
            	//获取当前角色对应的权限
                List<RolePermission> rolePermissions = role.getRolePermissions();
                for(RolePermission p : rolePermissions){
                    List<Permission> permissionList1 = p.getPermissionList();
                    for(Permission permission:permissionList1){
                        permissionList.add(permission.getResource());
                    }
                }
                //添加role
                roles.add(role.getSn());
            }
        }
       
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addStringPermissions(permissionList);
        simpleAuthorizationInfo.addRoles(roles);
        return simpleAuthorizationInfo;
    }

    /**
     * 获取登陆信息
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //我们在配置文件中使用了md5加密的方式处理
        //获取登录的用户名
        String username= (String)token.getPrincipal();
        System.out.println("authnc:"+username);
        //去数据库中查询它的账号密码
        UserInfo userInfo = userService.searchUserByUsername(username);
        if(userInfo==null){
            //直接返回null给判断器,他会判断抛出用户不存在的异常
            System.out.println("authc:"+null);
            return null;
        }
        //我们采用md5加密的方式,首先获取数据库中存放的加密后的密码
        String password = userInfo.getPassword();
        System.out.println("authc:pass:"+password);
        //设置用户名,密码,盐,realm名字返回给判断器和真实用户输入的密码进行对比
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(username+"666"),getName());
        return simpleAuthenticationInfo;
    }
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值