Shiro学习笔记(一)Shiro与Spring Boot的结合(两种方式)


后续篇:
Shiro学习笔记(二)后续内容记录
这篇文章里写了部分shiro功能的例子。

前言

本文是在b站学习Shiro时随笔记下,这个视频相比于网上一些文字教程还是比较详细,算是保姆级的教程。虽然时长较长,但对于初学还是比较推荐。

视频地址:https://www.bilibili.com/video/BV1pa4y1471s

本文旨在记录自己的学习过程,也是我学习笔记的第一篇记录,受我目前师父启发后发了这篇文章。我只是java小白一个,目前在实习,如有错误,欢迎指正。

一、JdbcRealm

1.MyBatis导入

<dependencies>
    <!--druid starter-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.20</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
</dependencies>

2.ShiroConfig

@Configuration
public class ShiroConfig {

    @Bean
    public JdbcRealm getJdbcRealm(DataSource dataSource) {
        JdbcRealm jdbcRealm = new JdbcRealm();
        //JdbcRealm会自行去数据区查询用户及权限数据(数据库的表结构要符合JdbcRealm的规范)
        jdbcRealm.setDataSource(dataSource);
        //JdbcRealm默认开启认证功能,需要手动开启授权功能
        jdbcRealm.setPermissionsLookupEnabled(true);
        return jdbcRealm;
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //securityManager要完成校验需要realm的支持
        securityManager.setRealm(jdbcRealm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean filter(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        //设置shiro的拦截规则
        /*
            anon    匿名用户可访问
            authc   认证用户可访问
            user    使用remenberme的用户可访问
            perms   对应权限可访问
            role    对应角色可访问
         */
        Map<String, String> filterMap = new HashMap<>();
        filterMap.put("/","anon");
        filterMap.put("/login.html","anon");
        filterMap.put("/index.html","anon");
        filterMap.put("/regist.html","anon");
        filterMap.put("/user/login","anon");
        filterMap.put("/user/regist","anon");
        filterMap.put("/static/**","anon");
        filterMap.put("/**","authc");

        filterFactoryBean.setFilterChainDefinitionMap(filterMap);
        filterFactoryBean.setLoginUrl("/login.html");
        //设置未授权访问的页面路径
        filterFactoryBean.setUnauthorizedUrl("/login.html");
        return filterFactoryBean;
    }
}

3.前端页面标签使用

JSP页面中引用:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
Thymeleaf模板中引用
(1)在pom.xml文件中导入thymeleaf模板对Shiro的标签支持的依赖
 <!--thymeleaf对shiro的支持-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
(2)在ShiroConfig中配置Shiro的方言支持
public class ShiroConfig(){
    
	@Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }
    
    //....
} 
(3)Thymeleaf模板中引入Shiro的命名空间
<html lang="en" 
      xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
</html>
常用的标签

1.shiro:guest

判断用户是否是游客身份,如果是游客身份则显示此标签内容

<shiro:guest>
    欢迎游客登录访问,<a href="login.html">登录</a>
</shiro:guest>

2.shiro:user

判断用户是否是认证身份,如果是认证身份则显示此标签内容

与guest标签效果相反

<shiro:user>
    已登录用户!
</shiro:user>

3.shiro:principal

取用户的登录信息

<shiro:user>
    欢迎<shiro:principal/>!
</shiro:user>

在这里插入图片描述
4.shiro:notAuthenticated/shiro:authenticated

与1、2标签的效果相同,认证过程不同

5.shiro:hasRole

判断当前登录的用户是否有指定的角色,有则显示指定内容

<shiro:user>
    欢迎【<shiro:principal/>】!
    当前用户为
    <shiro:hasRole name="admin">超级管理员</shiro:hasRole>
    <shiro:hasRole name="cmanager">仓管人员</shiro:hasRole>
    <shiro:hasRole name="smanager">销售人员</shiro:hasRole>
    <shiro:hasRole name="kmanager">客服人员</shiro:hasRole>
    <shiro:hasRole name="zmanager">行政人员</shiro:hasRole>
</shiro:user>

效果图:
在这里插入图片描述

6.shiro:hasPermission

判断当前登录的用户是否有指定的权限,有则显示指定内容

仓库管理
<ul>
    <shiro:hasPermission name="sys:c:save"><li><a href="#">入库</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:c:delete"><li><a href="#">出库</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:c:update"><li><a href="#">修改</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:c:find"><li><a href="#">查询</a></li></shiro:hasPermission>
</ul>

订单管理
<ul>
    <shiro:hasPermission name="sys:s:save"><li><a href="#">添加订单</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:s:delete"><li><a href="#">删除订单</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:s:update"><li><a href="#">修改订单</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:s:find"><li><a href="#">查询订单</a></li></shiro:hasPermission>
</ul>

客户管理
<ul>
    <shiro:hasPermission name="sys:k:save"><li><a href="#">添加客户</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:k:delete"><li><a href="#">删除客户</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:k:update"><li><a href="#">修改客户</a></li></shiro:hasPermission>
    <shiro:hasPermission name="sys:k:find"><li><a href="#">查询客户</a></li></shiro:hasPermission>
</ul>

效果图:
在这里插入图片描述

二、自定义Realm

1.MyBatis导入

<dependencies>
    <!--druid starter-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.20</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
</dependencies>

2.yml文件配置

此配置在JDBCRealm中也需要配置,配置基本相同,上面忘写了。

spring:
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/shirostudy
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root
      initial-size: 1
      min-idle: 1
      max-active: 20
mybatis:
  mapper-locations: classpath:mappers/*Mapper.xml
  type-aliases-package: com.wxy.shiro4.beans

3.数据库的创建

直接创建就可,无需格式,推荐五张表起步。用户表,角色表,用户-角色表,权限表,角色-权限表。

user

CREATE TABLE `user` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `user_name` varchar(60) CHARACTER SET utf8 NOT NULL COMMENT '用户名',
  `user_pwd` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '用户密码',
  `pwd_salt` varchar(30) CHARACTER SET utf8 DEFAULT NULL COMMENT '加盐',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1 COMMENT='用户表';

role

CREATE TABLE `role` (
  `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `role_name` varchar(60) CHARACTER SET utf8 NOT NULL COMMENT '角色名',
  `role_desc` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COMMENT='角色表';

user_role

CREATE TABLE `user_role` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户id',
  `role_id` bigint(20) NOT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=latin1 COMMENT='用户角色表';	

permission

CREATE TABLE `permission` (
  `per_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限id',
  `permission` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '权限名称',
  `per_desc` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '权限描述',
  PRIMARY KEY (`per_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1 COMMENT='权限表';

role_per

CREATE TABLE `role_per` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) NOT NULL COMMENT '角色id',
  `per_id` bigint(20) NOT NULL COMMENT '权限id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=latin1 COMMENT='角色权限表';

4.Dao实现

  • Shiro进行需要用户信息
    • 根据用户名查询用户信息
  • Shiro进行授权管理需要当前用户的角色和权限
    • 根据用户名查询当前用户的角色列表
    • 根据用户名查询当前用户的权限列表

以上三个查询可以直接连接查询(教程也是连接查询)

但我建议采用分开查询,在代码逻辑中进行连接查询可以提高效率。

这里教学中查询结果为单列角色名与权限名,但是我直接查询出List然后循环使用.get()方法。

以下为项目结构:
在这里插入图片描述

UserDao:

public interface UserDao {

    public User queryUserByUserName(String userName) throws Exception;

    public Integer queryUserIdByUserName(String userName);
}

RoleDao:

public interface RoleDao {
    public Role queryRoleListByRoleId(Integer roleId);
}

UserRoleDao:

public interface UserRoleDao {

    public List<Integer> queryRoleIdByUserId(Integer userId);
}

PermissionDao:

public interface PermissionDao {

    public Permission queryPermissionByPerId(Integer perId);
}

RolePerDao:

public interface RolePerDao {

    public List<Integer> queryPerIdByRoleId(Integer roleId);

}

UserMapper:

<resultMap id="userMap" type="com.wxy.shiro4.beans.User">
    <id column="user_id" property="userId"/>
    <result column="user_name" property="userName"/>
    <result column="user_pwd" property="userPwd"/>
    <result column="pwd_salt" property="pwdSalt"/>
</resultMap>

<select id="queryUserByUserName" resultMap="userMap">
    SELECT
    *
    FROM
    USER
    WHERE
    user_name = #{userName}
</select>

<select id="queryUserIdByUserName" resultType="integer">
    SELECT
    user_id
    FROM
    USER
    WHERE
    user_name = #{userName}
</select>

RoleMapper:

<resultMap id="roleMap" type="com.wxy.shiro4.beans.Role">
    <id column="role_id" property="roleId"/>
    <result column="role_name" property="roleName"/>
    <result column="role_desc" property="roleDesc"/>
</resultMap>
<select id="queryRoleListByRoleId" resultMap="roleMap">
    SELECT
    *
    FROM
    role
    WHERE
    role_id = #{roleId}
</select>

UserRoleMapper:

<select id="queryRoleIdByUserId" resultType="integer">
    SELECT
    role_id
    FROM
    user_role
    WHERE
    user_id = #{userId}
</select>

PermissionMapper

<resultMap id="permissionMap" type="com.wxy.shiro4.beans.Permission">
    <id column="per_id" property="perId"/>
    <result column="permisson" property="permission"/>
    <result column="per_desc" property="perDesc"/>
</resultMap>
<select id="queryPermissionByPerId" resultMap="permissionMap">
    SELECT
    *
    FROM
    permission
    WHERE
    per_id = #{perId}
</select>

RolePerMapper

<select id="queryPerIdByRoleId" resultType="integer">
    SELECT
    per_id
    FROM
    role_per
    WHERE
    role_id = #{roleId}
</select>

写完mapper之后,在启动类上加上@MapperScan(basePackages = "com.wxy.shiro4.dao")注释,找到dao的位置。

5.Shiro整合

导入依赖
shiro-spring 是spring对shiro的支持依赖
thymeleaf-extras-shiro是thymeleaf对shiro的支持依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.0</version>
</dependency>
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

基于java配置Shiro,ShiroConfig类

@Configuration
public class ShiroConfig {

    //Shiro的方言支持
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

    //自定义Realm
    @Bean
    public MyRealm getMyRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    //SecurityManager(安全管理器)
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //securityManager要完成校验需要realm的支持
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    //过滤器
    @Bean
    public ShiroFilterFactoryBean filter(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        //过滤器是Shiro
        filterFactoryBean.setSecurityManager(securityManager);
        //设置shiro的拦截规则
        /*
            anon    匿名用户可访问
            authc   认证用户可访问
            user    使用remenberme的用户可访问
            perms   对应权限可访问
            role    对应角色可访问
         */
        Map<String, String> filterMap = new HashMap<>();
        filterMap.put("/","anon");
        filterMap.put("/login.html","anon");
        filterMap.put("/index.html","anon");
        filterMap.put("/regist.html","anon");
        filterMap.put("/user/login","anon");
        filterMap.put("/user/regist","anon");
        filterMap.put("/static/**","anon");
        filterMap.put("/**","authc");

        filterFactoryBean.setFilterChainDefinitionMap(filterMap);
        filterFactoryBean.setLoginUrl("/login.html");
        //设置未授权访问的页面路径
        filterFactoryBean.setUnauthorizedUrl("/login.html");
        return filterFactoryBean;
    }


}

自定义Realm

Realm对外提供合法数据。里面有两个方法,doGetAuthenticationInfo为认证器提供数据,doGetAuthorizationInfo为授权器提供数据。

可以自定义多个realm,只是要在ShiroConfig中使用securityManager.setRealms()来设置多个Realm。

/**
 * 1.创建一个类继承AuthorizingRealm才能称为一个Realm(实现了Realm接口的类)
 * 2.重写doGetAuthorizationInfo和doGetAuthenticationInfo两个方法
 * 3.重写getName()方法,返回当前realm的一个自定义名称
 */
public class MyRealm extends AuthorizingRealm {

    @Resource
    private UserDao userDao;
    @Resource
    private RoleDao roleDao;
    @Resource
    private UserRoleDao userRoleDao;
    @Resource
    private PermissionDao permissionDao;
    @Resource
    private RolePerDao rolePerDao;

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

    /**
     * 获取授权数据(将当前用户的角色及权限信息查询出来)
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取当前用户的用户名
        String username = (String) principalCollection.iterator().next();
        //根据用户名查询当前用户的角色列表
        Integer userId = userDao.queryUserIdByUserName(username);
        List<Integer> roleIds = userRoleDao.queryRoleIdByUserId(userId);
        Set<String> roleNames = new HashSet<>();
        Set<String> perNames = new HashSet<>();
        for (Integer roleId : roleIds) {
            Role role = roleDao.queryRoleListByRoleId(roleId);
            roleNames.add(role.getRoleName());
            List<Integer> perIds = rolePerDao.queryPerIdByRoleId(roleId);
            //根据用户名查询当前用户的权限列表
            for (Integer perId : perIds) {
                Permission permission = permissionDao.queryPermissionByPerId(perId);
                perNames.add(permission.getPermission());
            }
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roleNames);
        info.setStringPermissions(perNames);
        return info;
    }

    /**
     * 获取认证的安全数据(从数据库查询的用户的正确数据)
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //参数authenticationToken就是传递的 subject.login(token)中的token
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //从token中获取用户名
        String username = token.getUsername();
        //根据用户名从数据库查询当前用户的安全数据
        User user = userDao.queryUserByUserName(username);
        if (user == null) {
            return null;
        }
        //把查询出来的安全信息放到AuthenticationInfo中
        AuthenticationInfo info = new SimpleAuthenticationInfo(
                username,           //当前用户用户名
                user.getUserPwd(),  //从数据库查询出来的安全密码
                getName()           //当前Realm名
        );
        return info;
    }

}

以上经测试可以成功,那么就实现了动态分配权限的效果。配合前端页面,可以实现不同用户登录看到不同页面的效果。

总结

在Spring Boot上使用Shiro其实非常方便,主要就是导入依赖,配置ShiroConfig,然后自定义Realm或者使用JDBCRealm;

使用JdbcRealm需要对数据库有严格要求,而自定义Realm只需要自己设计数据库就好了。

另外,我对于Shiro的理解为:Realm从数据库拿取合法数据,然后将数据提供给认证器和授权器,认证器和授权器拿到数据后返回给前台。

现在由于只是学习阶段,对这方面知识不够深入理解,以上只是我自己在看视频的时候的随笔笔记,其中部分思考都是我自己结合教学得出,如有错误,欢迎指正。

如有侵权,联系删除。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值