springboot整合shiro-快速入门(二)

使用springboot + mybatis-plus + thymleaf模板实现快速入门

添加依赖

注意: shiro-spring 和 thymeleaf-extras-shiro

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <!--shiro-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    <!-- shiro-thymeleaf 2.0.0-->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
		
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.2</version>
    </dependency>

</dependencies>

配置文件

application.properties
# 配置数据源
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/testshiro?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

# 关闭默认模板引擎缓存
spring.thymeleaf.cache=false

# mybatis-plus 配置
mybatis-plus.mapper-locations=classpath*:mapper/*.xml

server.port=18080

# 配置日志文件
logging.config=classpath:config/logback.xml
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="./logs" />

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg  %n</pattern>
        </encoder>
    </appender>

    <!-- 按照每天生成日志文件 -->
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/runtime.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

脚本初始化

数据库创建以下几个表(用户表,角色表,用户-角色表,权限表,角色-权限表)

user_info.sql(用户表)
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT '' COMMENT '用户名',
`password` varchar(50) DEFAULT NULL COMMENT '登录密码',
`name` varchar(50) DEFAULT NULL COMMENT '用户真实姓名',
`id_card_num` varchar(50) DEFAULT NULL COMMENT '用户身份证号',
`state` char(1) DEFAULT '0' COMMENT '用户状态 0:正常 1:锁定',
PRIMARY KEY (`uid`),
UNIQUE KEY `username` (`username`) USING BTREE,
UNIQUE KEY `id_card_num` (`id_card_num`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_role.sql(角色表)
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`available` char(1) DEFAULT '0' COMMENT '是否可用 0:可用  1:不可用',
`role` varchar(20) DEFAULT NULL COMMENT '角色标识程序中判断使用,如admin',
`description` varchar(100) DEFAULT NULL COMMENT '角色描述,UI界面显示使用',
PRIMARY KEY (`id`),
UNIQUE KEY `role` (`role`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_user_role.sql(用户-角色表)
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`uid` int(11) DEFAULT NULL COMMENT '用户id',
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
KEY `uid` (`uid`) USING BTREE,
KEY `role_id` (`role_id`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_permission.sql(权限表)
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`parent_id` int(11) DEFAULT NULL COMMENT '父编号,本权限可能是该父编号权限的子权限',
`parent_ids` int(20) DEFAULT NULL COMMENT '父编号列表',
`permission` varchar(100) DEFAULT NULL COMMENT '权限字符串,menu例子:role:* -- button例子:role:create,role:update,role:delete,role:view',
`resource_type` varchar(20) DEFAULT NULL COMMENT '资源类型 [menu | button]',
`url` varchar(100) DEFAULT NULL COMMENT '资源路径 如:/userinfo/list',
`name` varchar(50) DEFAULT NULL COMMENT '权限名称',
`available` char(1) DEFAULT '0' COMMENT '是否可用 0:可用  1:不可用',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_role_permission.sql(角色-权限表)
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
`permission_id` int(11) DEFAULT NULL COMMENT '权限id',
KEY `role_id` (`role_id`) USING BTREE,
KEY `permission_id` (`permission_id`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
添加数据
# 插入用户信息表
INSERT INTO `user_info`(`uid`, `username`, `password`, `name`, id_card_num) VALUES (null, 'admin', '123456', '宁缺', '133333333333333333');
INSERT INTO `user_info`(`uid`, `username`, `password`, `name`, id_card_num) VALUES (null, 'test', '123456', '桑桑', '155555555555555555');
INSERT INTO `user_info`(`uid`, `username`, `password`, `name`, id_card_num) VALUES (null, 'ray', '123456', '马云', '188888888888888888');
# 插入角色表
INSERT INTO `sys_role` (`id`, `available`, `description`, `role`) VALUES (null, 0, '管理员', 'admin');
INSERT INTO `sys_role` (`id`, `available`, `description`, `role`) VALUES (null, 0, 'VIP会员', 'vip');
INSERT INTO `sys_role` (`id`, `available`, `description`, `role`) VALUES (null, 1, '测试', 'test');
# 插入用户角色关联表
INSERT INTO `sys_user_role` (`role_id`, `uid`) VALUES (1, 1);
INSERT INTO `sys_user_role` (`role_id`, `uid`) VALUES (2, 2);
INSERT INTO `sys_user_role` (`role_id`, `uid`) VALUES (3, 3);

# 插入权限表
INSERT INTO `sys_permission` (`id`, `available`, `name`, `parent_id`, `parent_ids`, `permission`, `resource_type`, `url`) VALUES (null, 0, '用户管理', 0, '0/', 'userInfo:view', 'menu', 'userInfo/view');
INSERT INTO `sys_permission` (`id`, `available`, `name`, `parent_id`, `parent_ids`, `permission`, `resource_type`, `url`) VALUES (null, 0, '用户添加', 1, '0/1', 'userInfo:add', 'button', 'userInfo/add');
INSERT INTO `sys_permission` (`id`, `available`, `name`, `parent_id`, `parent_ids`, `permission`, `resource_type`, `url`) VALUES (null, 0, '用户删除', 1, '0/1', 'userInfo:del', 'button', 'userInfo/del');
#插入角色_权限表
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);

Entity

注意:不存在的字段,统一使用注解@TableField(exist = false)

用户信息
/**
 * Created by 李新宇
 * 2019-07-06 11:43
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserInfo implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "uid", type = IdType.AUTO)
    private Integer uid;

    /**
     * 用户名
     */
    private String username;

    /**
     * 登录密码
     */
    private String password;

    /**
     * 用户真实姓名
     */
    private String name;

    /**
     * 用户身份证号
     */
    private String idCardNum;

    /**
     * 用户状态 0:正常 1:锁定
     */
    private String state;

    @TableField(exist = false)
    private Set<SysRole> roles = new HashSet<>();
}
角色信息
/**
 * Created by 李新宇
 * 2019-07-06 11:03
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SysRole implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 是否可用 0:可用  1:不可用
     */
    private String available;

    /**
     * 角色标识程序中判断使用,如admin
     */
    private String role;

    /**
     * 角色描述,UI界面显示使用
     */
    private String description;

    @TableField(exist = false)
    private Set<UserInfo> users = new HashSet<>();
    @TableField(exist = false)
    private Set<SysPermission> permissions = new HashSet<>();
}
权限信息
/**
 * Created by 李新宇
 * 2019-07-06 11:42
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SysPermission implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 父编号,本权限可能是该父编号权限的子权限
     */
    private Integer parentId;

    /**
     * 父编号列表
     */
    private String parentIds;

    /**
     * 权限字符串,menu例子:role:* -- button例子:role:create,role:update,role:delete,role:view
     */
    private String permission;

    /**
     * 资源类型 [menu | button]
     */
    private String resourceType;

    /**
     * 资源路径 如:/userinfo/list
     */
    private String url;

    /**
     * 权限名称
     */
    private String name;

    /**
     * 是否可用 0:可用  1:不可用
     */
    private String available;

    @TableField(exist = false)
    private Set<SysRole> sysRoles = new HashSet<>();
}
角色权限关系表

/**
 * Created by 李新宇
 * 2019-07-06 11:42
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SysRolePermission implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 角色id
     */
    private Integer roleId;

    /**
     * 权限id
     */
    private Integer permissionId;

}

Mapper

注意:这里为了学习Shiro,加上了 my 前缀,不使用BaseMapper

UserInfoMapper

/**
 * Created by 李新宇
 * 2019-07-06 11:47
 */
public interface UserInfoMapper extends BaseMapper<UserInfo> {
    /**
     * 根据username查询信息
     */
    UserInfo myFindByUserInfoName(String username);

    /**
     * 根据userInfo插入信息
     */
    int myInsert(UserInfo userInfo);

    /**
     * 根据username删除信息
     */
    int myDel(@Param("username") String username);
}
SysRoleMapper
/**
 * Created by 李新宇
 * 2019-07-06 11:59
 */
public interface SysRoleMapper extends BaseMapper<SysRole> {
    /**
     * 根据uid查询角色信息 - 一个用户可能存在多个角色,故用Set
     */
    Set<SysRole> myFindSysRolesByUserInfoId(@Param("uid") Integer id);
}
SysPermissionMapper
/**
 * Created by 李新宇
 * 2019-07-06 11:43
 */
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
    /**
     * 根据sysRoles查询权限 - 一个角色可能存在多个权限,故用Set
     */
    Set<SysPermission> myFindSysPermissionsBySysRoleId(@Param("sysRoles") Set<SysRole> sysRoles);
}
SysRolePermissionMapper
/**
 * Created by 李新宇
 * 2019-07-06 11:43
 */
public interface SysRolePermissionMapper extends BaseMapper<SysRolePermission> {

}

MapperXML

UserInfoMapper.xml
<?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.ray.shiro_demo_01.mapper.UserInfoMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ray.shiro_demo_01.entity.UserInfo">
        <id column="uid" property="uid"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="name" property="name"/>
        <result column="id_card_num" property="idCardNum"/>
        <result column="state" property="state"/>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        uid, username, password, name, id_card_num, state
    </sql>

    <insert id="myInsert" parameterType="com.ray.shiro_demo_01.entity.UserInfo">
        INSERT INTO user_info
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="uid != null">
                uid,
            </if>
            <if test="username != null and username != ''">
                username,
            </if>
            <if test="password != null and password != ''">
                password,
            </if>
            <if test="name != null and name != ''">
                `name`,
            </if>
            <if test="idCardNum != null and idCardNum != ''">
                id_card_num,
            </if>
            <if test="state != null and state != ''">
                state,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="uid != null">
                #{uid},
            </if>
            <if test="username != null and username != ''">
                #{username},
            </if>
            <if test="password != null and password != ''">
                #{password},
            </if>
            <if test="name != null and name != ''">
                #{name},
            </if>
            <if test="idCardNum != null and idCardNum != ''">
                #{idCardNum},
            </if>
            <if test="state != null and state != ''">
                #{state},
            </if>
        </trim>
    </insert>

    <delete id="myDel">
        DELETE FROM user_info WHERE username = #{username}
    </delete>

    <select id="myFindByUserInfoName" resultType="com.ray.shiro_demo_01.entity.UserInfo">
        SELECT * FROM user_info WHERE username = #{username}
    </select>
</mapper>
SysRoleMapper.xml
<?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.ray.shiro_demo_01.mapper.SysRoleMapper">

    <select id="myFindSysRolesByUserInfoId" resultType="com.ray.shiro_demo_01.entity.SysRole">
        SELECT
        r.*
        FROM
        sys_role r
        LEFT JOIN
        sys_user_role ur
        ON
        r.id = ur.role_id
        WHERE
        ur.uid = #{uid}
    </select>
</mapper>
SysPermissionMapper.xml
<?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.ray.shiro_demo_01.mapper.SysPermissionMapper">

    <select id="myFindSysPermissionsBySysRoleId" resultType="com.ray.shiro_demo_01.entity.SysPermission">
        SELECT
        p.*
        FROM
        sys_permission p
        LEFT JOIN
        sys_role_permission rp
        ON
        p.id = rp.permission_id
        WHERE
        rp.role_id
        IN
        <foreach collection="sysRoles" index="index" item="item" open="(" close=")" separator=",">
            #{item.id}
        </foreach>
    </select>
</mapper>
SysRolePermissionMapper.xml
<?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.ray.shiro_demo_01.mapper.SysRolePermissionMapper">

</mapper>

Shiro 配置

自定义ShiroRealm

在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO。

/**
 * Created by 李新宇
 * 2019-07-06 11:32
 * 在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的
 * 在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
 */
public class ShiroRealm extends AuthorizingRealm {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private SysRoleMapper sysRoleMapper;

    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    /**
     * 验证用户身份
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //获取用户名密码 第一种方式
//        String username = (String) authenticationToken.getPrincipal();
//        String password = new String((char[]) authenticationToken.getCredentials());

        // 第二种方式
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        String password = new String(usernamePasswordToken.getPassword());

        //从数据库查询用户信息
        UserInfo user = this.userInfoMapper.myFindByUserInfoName(username);

        //可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验
        //调用 CredentialsMatcher 校验 还需要创建一个类 继承CredentialsMatcher  如果在上面校验了,这个就不需要了
        if (user == null) {
            throw new UnknownAccountException("用户名或密码错误!");
        }
        if (!password.equals(user.getPassword())) {
            throw new IncorrectCredentialsException("用户名或密码错误!");
        }
        if ("1".equals(user.getState())) {
            throw new LockedAccountException("账号已被锁定,请联系管理员!");
        }

        //配置自定义权限登录器 参考博客:
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
        return info;
    }

    /**
     * 授权用户权限
     * 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
     * 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
     * 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
     * <p>
     * shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();
     * 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
     * 所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
     * <p>
     * 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。
     * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
     * <p>
     * 当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
     * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
     * <p>
     * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
     * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
     * <p>
     * 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002],perms[权限添加]");
     * 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        // 获取用户
        UserInfo user = (UserInfo) SecurityUtils.getSubject().getPrincipal();

        //  获取用户角色
        Set<SysRole> roles = this.sysRoleMapper.myFindSysRolesByUserInfoId(user.getUid());
        // 添加角色
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        for (SysRole role : roles) {
            authorizationInfo.addRole(role.getRole());
        }

        // 获取用户权限
        Set<SysPermission> permissions = this.sysPermissionMapper.myFindSysPermissionsBySysRoleId(roles);
        // 添加权限
        for (SysPermission permission : permissions) {
            authorizationInfo.addStringPermission(permission.getPermission());
        }
        return authorizationInfo;
    }
Shiro配置类
/**
 * Created by 李新宇
 * 2019-07-06 11:50
 * Shiro 配置类
 */
@Configuration
public class ShiroConfig {

    /**
     * 开启shiro 注解模式
     * 可以在controller中的方法前添加注解
     * 如 @RequiresPermissions("userInfo:add")
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     * Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截
     * @param securityManager
     * @return
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager, Shiro的核心安全接口
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 这里的 /login 是后台的接口名,非页面,如果不设置默认会自动寻找Web工程根目录下的 "/login.html" 页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 这里的 /index 是后台的接口名,非页面,登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权跳转的链接
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        //自定义拦截器限制并发人数,参考博客:
        //LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        //限制同一帐号同时在线的个数
        //filtersMap.put("kickout", kickoutSessionControlFilter());
        //shiroFilterFactoryBean.setFilters(filtersMap);

        // 配置访问权限 必须是LinkedHashMap,因为它必须保证有序
        // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 一定要注意顺序,否则就不好使了
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //配置不登录可以访问的资源,anon 表示资源都可以匿名访问
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
//        filterChainDefinitionMap.put("/druid/**", "anon");
        // logout 是 shiro提供的过滤器
        filterChainDefinitionMap.put("/logout", "logout");

        //此时访问/userInfo/del需要del权限,在自定义Realm中为用户授权。
        filterChainDefinitionMap.put("/userInfo/del", "perms[userInfo:del]");
        filterChainDefinitionMap.put("/userInfo/add", "perms[userInfo:add]");
        filterChainDefinitionMap.put("/userInfo/view", "perms[userInfo:view]");

        //其他资源都需要认证  authc 表示需要认证才能进行访问
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /**
     * 配置核心安全事务管理器
     * @param shiroRealm
     * @return
     */
    @Bean(name = "securityManager")
    public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 设置自定义realm
        securityManager.setRealm(shiroRealm);

        //配置记住我 参考博客:
        //securityManager.setRememberMeManager(rememberMeManager());

        //配置 redis缓存管理器 参考博客:
        //securityManager.setCacheManager(getEhCacheManager());

        //配置自定义session管理,使用redis 参考博客:
        //securityManager.setSessionManager(sessionManager());

        return securityManager;
    }

    /**
     * 配置Shiro生命周期处理器
     * @return
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
     * @return
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        return shiroRealm;
    }

    /**
     * 必须(thymeleaf页面使用shiro标签控制按钮是否显示)
     * 未引入thymeleaf包,Caused by: java.lang.ClassNotFoundException: org.thymeleaf.dialect.AbstractProcessorDialect
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}
内置的FilterChain(常用)

这几个是我们常用到的,在这里说明下,其它的请自行查询文档进行学习。

  1. anon:所有url都都可以匿名访问;
  2. authc: 需要认证才能进行访问;
  3. user:配置记住我或认证通过可以访问;

MP 配置

简单测试,不创建配置类,直接在启动类配置

@SpringBootApplication
@MapperScan("com.ray.shiro_demo_01.mapper")
public class ShiroDemo01Application {

    public static void main(String[] args) {
        SpringApplication.run(ShiroDemo01Application.class, args);
    }
}

Controller

LoginController
/**
 * Created by 李新宇
 * 2019-07-06 11:30
 */
@Controller
public class LoginController {

    /**
     * 访问项目根路径
     */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String root(Model model) {
        Subject subject = SecurityUtils.getSubject();
        UserInfo user = (UserInfo) subject.getPrincipal();
        if (user == null) {
            return "redirect:/login";
        } else {
            return "redirect:/index";
        }
    }

    /**
     * 跳转到login页面
     */
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(Model model) {
        Subject subject = SecurityUtils.getSubject();
        UserInfo user = (UserInfo) subject.getPrincipal();
        if (user == null) {
            return "login";
        } else {
            return "redirect:index";
        }
    }

    /**
     * 用户登录
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String loginUser(HttpServletRequest request, String username, String password, Model model, HttpSession session) {

        //对密码进行加密
        //password=new SimpleHash("md5", password, ByteSource.Util.bytes(username.toLowerCase() + "shiro"),2).toHex();
        //如果有点击  记住我
        //UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password,remeberMe);

        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();

        try {
            // 登录操作
            subject.login(usernamePasswordToken);
            UserInfo user = (UserInfo) subject.getPrincipal();
            // 更新用户登录时间,也可以在ShiroRealm里做
            session.setAttribute("user", user);
            model.addAttribute("user", user);
            return "index";
        } catch (Exception e) {
            //登录失败从request中获取shiro处理的异常信息 shiroLoginFailure:就是shiro异常类的全类名
            String exception = (String) request.getAttribute("shiroLoginFailure");
            model.addAttribute("msg", e.getMessage());
            //返回登录页面
            return "login";
        }
    }

    @RequestMapping("/index")
    public String index(HttpSession session, Model model) {
        Subject subject = SecurityUtils.getSubject();
        UserInfo user = (UserInfo) subject.getPrincipal();
        if (user == null) {
            return "login";
        } else {
            model.addAttribute("user", user);
            return "index";
        }
    }

    /**
     * 登出  这个方法没用到,用的是shiro默认的logout
     */
    @RequestMapping("/logout")
    public String logout(HttpSession session, Model model) {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        model.addAttribute("msg", "安全退出");
        return "login";
    }

    /**
     * 跳转到无权限页面
     */
    @RequestMapping("/unauthorized")
    public String unauthorized(HttpSession session, Model model) {
        return "unauthorized";
    }
}
UserController
/**
 * Created by 李新宇
 * 2019-07-06 11:30
 */
@RestController
@RequestMapping("userInfo")
public class UserController {

    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 创建固定写死的用户
     */
    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String login(Model model) {
        UserInfo user = new UserInfo();
        user.setName("波波");
        user.setIdCardNum("177777777777777777");
        user.setUsername("bobo");

        userInfoMapper.myInsert(user);

        return "创建用户成功";
    }

    /**
     * 删除固定写死的用户
     */
//    @RequiresPermissions("userInfo:del")
    @RequestMapping(value = "/del", method = RequestMethod.GET)
    public String del(Model model) {
        userInfoMapper.myDel("bobo");

        return "删除用户名 bobo 成功";
    }

    /**
     * 用户列表
     */
    @RequestMapping(value = "/view", method = RequestMethod.GET)
    public String view(Model model) {
        return "用户列表";
    }
}

View

login.html
<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>login.html</title>
</head>
<body>
<h1>欢迎登录</h1>
<h1 th:if="${msg != null}" th:text="${msg}" style="color:red"></h1>
<form action="/login" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>index.html</title>
</head>
<body>
<h1 th:text="'欢迎' + ${user.username} + '光临!请选择你的操作'"></h1>
<br>

<ul>
    <h1 th:if="${msg != null}" th:text="${msg}" style="color: red"></h1>

    <shiro:hasPermission name="userInfo:add"><a href="/userInfo/add">点击添加固定用户信息</a> </shiro:hasPermission><br>
    <shiro:hasPermission name="userInfo:del"><a href="/userInfo/del">点击删除固定用户信息</a> </shiro:hasPermission><br>
    <shiro:hasPermission name="userInfo:view"><a href="/userInfo/view">显示此内容表示拥有查看用户列表的权限</a> </shiro:hasPermission><br>

    <!--&lt;!&ndash; 用户没有身份验证时显示相应信息,即游客访问信息 &ndash;&gt;-->
    <shiro:guest>游客显示的信息</shiro:guest><br>

    <!--&lt;!&ndash; 用户已经身份验证/记住我登录后显示相应的信息 &ndash;&gt;-->
    <shiro:user>用户已经登录过了</shiro:user><br>

    <!--&lt;!&ndash; 用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的 &ndash;&gt;-->
    <shiro:authenticated>不是记住我登录</shiro:authenticated><br>

    <!--&lt;!&ndash; 显示用户身份信息,通常为登录帐号信息,默认调用Subject.getPrincipal()获取,即Primary Principal &ndash;&gt;-->
    <shiro:principal></shiro:principal><br>

    <!--用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证,与guest标签的区别是,该标签包含已记住用户 -->
    <shiro:notAuthenticated>已记住用户</shiro:notAuthenticated><br>

    <!-- 相当于Subject.getPrincipals().oneByType(String.class) -->
    <shiro:principal type="java.lang.String" /><br>

    <!-- 相当于((User)Subject.getPrincipals()).getUsername() -->
    <shiro:principal property="username" /><br>

    <!-- 如果当前Subject有角色将显示body体内容 name="角色名" -->
    <shiro:hasRole name="admin">这是admin角色</shiro:hasRole><br>

    <!-- 如果当前Subject有任意一个角色(或的关系)将显示body体内容。 name="角色名1,角色名2..." -->
    <shiro:hasAnyRoles name="admin, vip">用户拥有admin角色或vip角色</shiro:hasAnyRoles><br>

    <!-- 如果当前Subject没有角色将显示body体内容 -->
    <shiro:lacksRole name="admin">如果不是admin角色,显示内容</shiro:lacksRole><br>

    <!-- 如果当前Subject有权限将显示body体内容 name="权限名" -->
    <shiro:hasPermission name="userInfo:add">用户拥有添加权限</shiro:hasPermission><br>

    <!-- 用户同时拥有以下两种权限,显示内容 -->
    <shiro:hasAllPermissions name="userInfo:add, userInfo:view">用户同时拥有列表权限和添加权限</shiro:hasAllPermissions><br>

    <!-- 用户拥有以下权限任意一种 -->
    <shiro:hasAnyPermissions name="userInfo:view, userInfo:del">用户拥有列表权限或者删除权限</shiro:hasAnyPermissions><br>

    <!-- 如果当前Subject没有权限将显示body体内容 name="权限名" -->
    <shiro:lacksPermission name="userInfo:add">如果用户没有添加权限,显示的内容</shiro:lacksPermission><br>
</ul>

<a href="/logout">点我注销</a>
</body>
</html>
unauthorized.html
<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>unauthorized.html</title>
</head>
<body>
<h1>对不起,您没有权限</h1>
</body>
</html>

验证测试

第一步: 访问 http://localhost:18080/userInfo/add 发现自动跳转到登录页

第二步:使用 admin/123456 登录
在这里插入图片描述
第三步:注销之后使用 test/123456 登录
在这里插入图片描述
不同的用户登录,显示不同的功能,点击之后也可以调用后台服务,证明身份验证成功。

test用户拥有 userInfo/del 权限
第四步: test用户使用尝试访问 http://localhost:8080/userInfo/add 发现没有权限


项目结构
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值