SpringBoot使用Shiro实现权限验证

版权声明: https://blog.csdn.net/typ1805/article/details/82989216

一、Shiro简介

          Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

主要功能

三个核心组件:Subject、SecurityManager 和 Realms.

  • Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
  • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
  • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

  Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

二、创建工程测试

1、简单了解Shiro的API

添加相关依赖pom.xml

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

		<!--shiro核心类库-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.2.3</version>
		</dependency>

		<!-- logback 依赖包-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>log4j-over-slf4j</artifactId>
		</dependency>

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

编写shiro配置文件,在resources目录下编写shiro配置文件,shiro.ini

#用户名=密码,角色1,角色2...,角色n
[users]
root = secret, admin
guest = guest, guest
test = 123456, role1, role2
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# 角色名=权限1,权限2...权限n
# -----------------------------------------------------------------------------
[roles]
admin = *
guest = guest
role1=perm1,perm2
role2=perm3

测试类

package com.example.demo.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;


/**
 * 路径:com.example.demo.shiro
 * 类名:
 * 功能:《用一句描述一下》
 * 备注:
 * 创建人:typ
 * 创建时间:2018/10/8 17:50
 * 修改人:
 * 修改备注:
 * 修改时间:
 */
@Slf4j
public class ShiroTest {

    public static void main(String[] args) {
        //创建 SecurityManager
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

        //解析配置文件,并返回一些 SecurityManager
        SecurityManager securityManager = factory.getInstance();

        //将SecurityManager
        SecurityUtils.setSecurityManager(securityManager);

        //安全操作,Subject
        Subject currentUser = SecurityUtils.getSubject();

        //测试在应用的当前会话中设置的属性
        Session session = currentUser.getSession();

        //放进去一个key
        session.setAttribute("someKey", "aValue");

        //根据key获取value
        String value = (String) session.getAttribute("someKey");
        //比较拿到的值和原来的值是否一致
        if ("aValue".equals(value)) {
            log.info("检索到正确的值[" + value + "]");
        }

        //尝试进行登录用户,如果登录失败了,我们进行一些处理
        if (!currentUser.isAuthenticated()) {
            //如果用户没有登录过
            UsernamePasswordToken token = new UsernamePasswordToken("test", "123456");
            //是否记住用户
            token.setRememberMe(true);
            try {
                currentUser.login(token);
                //当我们获登录用户之后
                log.info("用户 [" + currentUser.getPrincipal() + "] 登陆成功");
                // 查看用户是否有指定的角色
                if (currentUser.hasRole("admin")) {
                    log.info("您有admin角色");
                } else {
                    log.info("您没有admin角色");
                }
                if (currentUser.hasRole("role1")) {
                    log.info("您有role1角色");
                } else {
                    log.info("您没有role1角色");
                }

                // 查看用户是否有某个权限
                if (currentUser.isPermitted("perm1")) {
                    log.info("您有perm1权限");
                } else {
                    log.info("您没有perm1权限");
                }
                if (currentUser.isPermitted("guest")) {
                    log.info("您有guest权限");
                } else {
                    log.info("您没有guest权限");
                }

                //退出
                currentUser.logout();
            } catch (UnknownAccountException uae) {
                log.info(token.getPrincipal() + "账户不存在");
            } catch (IncorrectCredentialsException ice) {
                log.info(token.getPrincipal() + "密码不正确");
            } catch (LockedAccountException lae) {
                log.info(token.getPrincipal() + "用户被锁定了 ");
            } catch (AuthenticationException ae) {
                //无法判断是什么错了
                log.info(ae.getMessage());
            }

        }
    }

}

运行程序,查看结果如下:

21:41:25.606 [main] DEBUG org.apache.shiro.subject.support.DefaultSubjectContext - No SecurityManager available in subject context map.  Falling back to SecurityUtils.getSecurityManager() lookup.
21:41:25.610 [main] INFO com.example.demo.shiro.ShiroTest - 用户 [test] 登陆成功
21:41:25.610 [main] INFO com.example.demo.shiro.ShiroTest - 您没有admin角色
21:41:25.610 [main] INFO com.example.demo.shiro.ShiroTest - 您有role1角色
21:41:25.610 [main] INFO com.example.demo.shiro.ShiroTest - 您有perm1权限
21:41:25.634 [main] INFO com.example.demo.shiro.ShiroTest - 您没有guest权限
21:41:25.634 [main] DEBUG org.apache.shiro.mgt.DefaultSecurityManager - Logging out subject with primary principal test

2、Shiro+MySQL动态权限验证

数据库设计:

用户表(SHIRO_USER)、用户角色表(SHIRO_USER_ROLE)、角色权限表(SHIRO_ROLE_PERMISSION)

SQL如下:

# Host: 127.0.0.1  (Version 5.7.21)
# Date: 2018-10-08 22:26:32
# Generator: MySQL-Front 6.0  (Build 2.20)


#
# Structure for table "shiro_user"
#

DROP TABLE IF EXISTS `shiro_user`;
CREATE TABLE `shiro_user` (
  `id` varchar(32) DEFAULT NULL,
  `user_name` varchar(50) DEFAULT NULL,
  `password` varchar(32) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

#
# Data for table "shiro_user"
#

INSERT INTO `shiro_user` VALUES ('1','[email protected]','123456');


#
# Structure for table "shiro_user_role"
#

DROP TABLE IF EXISTS `shiro_user_role`;
CREATE TABLE `shiro_user_role` (
  `id` varchar(32) DEFAULT NULL,
  `role_name` varchar(50) DEFAULT NULL,
  `user_name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

#
# Data for table "shiro_user_role"
#

INSERT INTO `shiro_user_role` VALUES ('1','admin','[email protected]'),('2','test','[email protected]');

#
# Structure for table "shiro_role_permission"
#

DROP TABLE IF EXISTS `shiro_role_permission`;
CREATE TABLE `shiro_role_permission` (
  `id` varchar(32) DEFAULT NULL,
  `role_name` varchar(50) DEFAULT NULL,
  `perm_name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

#
# Data for table "shiro_role_permission"
#

INSERT INTO `shiro_role_permission` VALUES ('1','admin','perm1'),('2','test','guest');

添加数据库相关的依赖,pom.xml

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

		<!--shiro核心类库-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.2.3</version>
		</dependency>

		<!-- logback 依赖包-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>log4j-over-slf4j</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.46</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
		</dependency>

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

编写shiro配置文件,在resources目录下编写shiro配置文件,shiro-mysql.ini

[main]
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://127.0.0.1:3306/mc_config
dataSource.username=root
dataSource.password=admin
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#是否检查权限
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.dataSource=$dataSource
#重写sql语句
#根据用户名查询出密码
jdbcRealm.authenticationQuery = select PASSWORD from SHIRO_USER where USER_NAME = ?
#根据用户名查询出角色
jdbcRealm.userRolesQuery = select ROLE_NAME from SHIRO_USER_ROLE where USER_NAME = ?
#根据角色名查询出权限
jdbcRealm.permissionsQuery = select PERM_NAME from SHIRO_ROLE_PERMISSION WHERE ROLE_NAME = ?
securityManager.realms=$jdbcRealm

测试类

package com.example.demo.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;

/**
 * 路径:com.example.demo.shiro
 * 类名:
 * 功能:Shiro+MySQL动态权限验证
 * 备注:
 * 创建人:typ
 * 创建时间:2018/10/8 21:57
 * 修改人:
 * 修改备注:
 * 修改时间:
 */
@Slf4j
public class ShiroMysqlTest {

    public static void main(String[] args) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-mysql.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("[email protected]", "123456");
        //是否记住用户
        token.setRememberMe(true);
        try {
            currentUser.login(token);
            //当我们获登录用户之后
            log.info("用户 [" + currentUser.getPrincipal() + "] 登陆成功");
            //查看用户是否有角色
            if (currentUser.hasRole("admin")) {
                log.info("您有admin角色");
            } else {
                log.info("您没有admin角色");
            }
            if (currentUser.hasRole("test")) {
                log.info("您有test角色");
            } else {
                log.info("您没有test角色");
            }
            // 查看用户是否有某个权限
            if (currentUser.isPermitted("perm1")) {
                log.info("您有perm1权限");
            } else {
                log.info("您没有perm1权限");
            }
            if (currentUser.isPermitted("guest")) {
                log.info("您有guest权限");
            } else {
                log.info("您没有guest权限");
            }
            //退出
            currentUser.logout();
        } catch (UnknownAccountException uae) {
            log.info(token.getPrincipal() + "账户不存在");
        } catch (IncorrectCredentialsException ice) {
            log.info(token.getPrincipal() + "密码不正确");
        } catch (LockedAccountException lae) {
            log.info(token.getPrincipal() + "用户被锁定了 ");
        } catch (AuthenticationException ae) {
            //无法判断是什么错了
            log.info(ae.getMessage());
        }
    }

}

运行程序,查看结果如下:

21:48:00.094 [main] INFO com.example.demo.shiro.ShiroMysqlTest - 用户 [[email protected]] 登陆成功
21:48:00.098 [main] DEBUG org.apache.shiro.realm.AuthorizingRealm - No authorizationCache instance set.  Checking for a cacheManager...
21:48:00.098 [main] INFO org.apache.shiro.realm.AuthorizingRealm - No cache or cacheManager properties have been set.  Authorization cache cannot be obtained.
21:48:00.102 [main] INFO com.example.demo.shiro.ShiroMysqlTest - 您有admin角色
21:48:00.102 [main] DEBUG org.apache.shiro.realm.AuthorizingRealm - No authorizationCache instance set.  Checking for a cacheManager...
21:48:00.102 [main] INFO org.apache.shiro.realm.AuthorizingRealm - No cache or cacheManager properties have been set.  Authorization cache cannot be obtained.
21:48:00.110 [main] INFO com.example.demo.shiro.ShiroMysqlTest - 您有test角色
21:48:00.110 [main] DEBUG org.apache.shiro.realm.AuthorizingRealm - No authorizationCache instance set.  Checking for a cacheManager...
21:48:00.110 [main] INFO org.apache.shiro.realm.AuthorizingRealm - No cache or cacheManager properties have been set.  Authorization cache cannot be obtained.
21:48:00.114 [main] INFO com.example.demo.shiro.ShiroMysqlTest - 您有perm1权限
21:48:00.114 [main] DEBUG org.apache.shiro.realm.AuthorizingRealm - No authorizationCache instance set.  Checking for a cacheManager...
21:48:00.114 [main] INFO org.apache.shiro.realm.AuthorizingRealm - No cache or cacheManager properties have been set.  Authorization cache cannot be obtained.
21:48:00.122 [main] INFO com.example.demo.shiro.ShiroMysqlTest - 您有guest权限
21:48:00.122 [main] DEBUG org.apache.shiro.mgt.DefaultSecurityManager - Logging out subject with primary principal [email protected]

3. SpringBoot整合mybatis、shiro、redis实现基于数据库动态权限管理系统实例

数据库设计:

使用maven管理jar包,添加相关依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example.demo.shiro</groupId>
	<artifactId>shiro-mybatis-redis</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>shiro-mybatis-redis</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>tk.mybatis</groupId>
			<artifactId>mapper-spring-boot-starter</artifactId>
			<version>1.1.3</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.0</version>
		</dependency>
		<dependency>
			<groupId>tk.mybatis</groupId>
			<artifactId>mapper</artifactId>
			<version>3.4.2</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.28</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.46</version>
		</dependency>

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

		<dependency>
			<groupId>com.github.theborakompanioni</groupId>
			<artifactId>thymeleaf-extras-shiro</artifactId>
			<version>1.2.1</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.3.2</version>
		</dependency>

		<dependency>
			<groupId>net.sourceforge.nekohtml</groupId>
			<artifactId>nekohtml</artifactId>
			<version>1.9.22</version>
		</dependency>
		<dependency>
			<groupId>org.crazycake</groupId>
			<artifactId>shiro-redis</artifactId>
			<version>2.4.2.1-RELEASE</version>
			<exclusions>
				<exclusion>
					<artifactId>shiro-core</artifactId>
					<groupId>org.apache.shiro</groupId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
			<version>1.1.2</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

application.yml配置文件:

server:
  port: 8081

#数据源配置
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/shiro
    username: root
    password: admin
    initial-size: 1
    min-idle: 1
    max-active: 20
  thymeleaf:
    cache: false
    mode: LEGACYHTML5
  redis:
    host: 192.168.0.131
    port: 6379
    password: root
    timeout: 5000
    commandTimeout: 5000

mybatis:
  type-aliases-package: com.example.demo.shiro.entity
  mapper-locations: classpath:mapper/*.xml
  configLocation: classpath:mybatis.xml

mapper:
  identity: MYSQL
  mappers: com.example.demo.shiro.utils.MyMapper
  not-empty: false


#分页配置
pagehelper:
  helper-dialect: mysql
  offset-as-page-num: true
  reasonable: true
  row-bounds-with-count: true
  support-methods-arguments: true
  params: count=countSql
logging:
  level: debug

shiro的配置文件:

package com.example.demo.shiro.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.example.demo.shiro.entity.Resources;
import com.example.demo.shiro.service.ResourcesService;
import com.example.demo.shiro.shiro.MyShiroRealm;
import com.github.pagehelper.util.StringUtil;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 路径:com.example.demo.shiro.config
 * 类名:
 * 功能:《用一句描述一下》
 * 备注:
 * 创建人:typ
 * 创建时间:2018/10/9 10:22
 * 修改人:
 * 修改备注:
 * 修改时间:
 */
@Configuration
public class ShiroConfig {

    @Autowired
    private ResourcesService resourcesService;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    
    /**
     * 方法名:
     * 功能:ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
     * 描述:
     * 创建人:typ
     * 创建时间:2018/10/9 10:24
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    /**
     * 方法名:
     * 功能:ShiroFilterFactoryBean 处理拦截资源文件问题。
     *      注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     * 描述: Filter Chain定义说明
     *        1、一个URL可以配置多个Filter,使用逗号分隔
     *        2、当设置多个过滤器时,全部验证通过,才视为通过
     *        3、部分过滤器可指定参数,如perms,roles
     * 创建人:typ
     * 创建时间:2018/10/9 10:25
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/usersPage");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/img/**","anon");
        filterChainDefinitionMap.put("/font-awesome/**","anon");
        //<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        //自定义加载权限资源关系
        List<Resources> resourcesList = resourcesService.queryAll();
        for(Resources resources:resourcesList){
            if (StringUtil.isNotEmpty(resources.getResurl())) {
                String permission = "perms[" + resources.getResurl()+ "]";
                filterChainDefinitionMap.put(resources.getResurl(),permission);
            }
        }
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        //设置realm.
        securityManager.setRealm(myShiroRealm());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    /**
     * 方法名:
     * 功能:凭证匹配器
     *     (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了所以我们需要修改下doGetAuthenticationInfo中的代码;)
     * 描述:
     * 创建人:typ
     * 创建时间:2018/10/9 10:29
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    /**
     * 方法名:
     * 功能:开启shiro aop注解支持.
     * 描述:使用代理方式;所以需要开启代码支持;
     * 创建人:typ
     * 创建时间:2018/10/9 10:30
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 方法名:
     * 功能:配置shiro redisManager
     * 描述:使用的是shiro-redis开源插件
     * 创建人:typ
     * 创建时间:2018/10/9 10:30
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        // 配置缓存过期时间
        redisManager.setExpire(1800);
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * 方法名:
     * 功能:cacheManager 缓存 redis实现
     * 描述:使用的是shiro-redis开源插件
     * 创建人:typ
     * 创建时间:2018/10/9 10:31
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * 方法名:
     * 功能:RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 描述:使用的是shiro-redis开源插件
     * 创建人:typ
     * 创建时间:2018/10/9 10:31
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 方法名:
     * 功能: shiro session的管理
     * 描述:
     * 创建人:typ
     * 创建时间:2018/10/9 10:31
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

}

redis的配置:

package com.example.demo.shiro.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 路径:com.example.demo.shiro.config
 * 类名:
 * 功能:《用一句描述一下》
 * 备注:
 * 创建人:typ
 * 创建时间:2018/10/9 10:09
 * 修改人:
 * 修改备注:
 * 修改时间:
 */
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private Integer port;
    @Value("${spring.redis.timeout}")
    private Integer timeout;
    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public JedisPool redisPoolFactory(){
        log.info("JedisPool注入成功!!");
        log.info("redis地址:" + host + ":" + port);
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        JedisPool jedisPool = new JedisPool(jedisPoolConfig,host, port,timeout,password);
        return jedisPool;
    }
}

配置自定义的Realm

package com.example.demo.shiro.shiro;

import com.example.demo.shiro.entity.Resources;
import com.example.demo.shiro.entity.User;
import com.example.demo.shiro.service.ResourcesService;
import com.example.demo.shiro.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.ByteSource;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.util.*;

/**
 * 路径:com.example.demo.shiro.shiro
 * 类名:
 * 功能:《用一句描述一下》
 * 备注:
 * 创建人:typ
 * 创建时间:2018/10/9 10:32
 * 修改人:
 * 修改备注:
 * 修改时间:
 */
public class MyShiroRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Resource
    private ResourcesService resourcesService;

    @Autowired
    private RedisSessionDAO redisSessionDAO;

    /**
     * 方法名:
     * 功能:授权
     * 描述:
     * 创建人:typ
     * 创建时间:2018/10/9 10:35
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user= (User) SecurityUtils.getSubject().getPrincipal();
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("userid",user.getId());
        List<Resources> resourcesList = resourcesService.loadUserResources(map);
        // 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        for(Resources resources: resourcesList){
            info.addStringPermission(resources.getResurl());
        }
        return info;
    }

    /**
     * 方法名:
     * 功能:认证
     * 描述:
     * 创建人:typ
     * 创建时间:2018/10/9 10:35
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();
        User user = userService.selectByUsername(username);
        if(user==null) throw new UnknownAccountException();
        if (0==user.getEnable()) {
            throw new LockedAccountException(); // 帐号锁定
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user, //用户
                user.getPassword(), //密码
                ByteSource.Util.bytes(username),
                getName()  //realm name
        );
        // 当验证都通过后,把用户信息放在session里
        Session session = SecurityUtils.getSubject().getSession();
        session.setAttribute("userSession", user);
        session.setAttribute("userSessionId", user.getId());
        return authenticationInfo;
    }

    /**
     * 方法名:
     * 功能:根据userId 清除当前session存在的用户的权限缓存
     * 描述:userIds 已经修改了权限的userId
     * 创建人:typ
     * 创建时间:2018/10/9 10:34
     * 修改人:
     * 修改描述:
     * 修改时间:
     */
    public void clearUserAuthByUserId(List<Integer> userIds){
        if(null == userIds || userIds.size() == 0)	return ;
        //获取所有session
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        //定义返回
        List<SimplePrincipalCollection> list = new ArrayList<SimplePrincipalCollection>();
        for (Session session:sessions){
            //获取session登录信息。
            Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if(null != obj && obj instanceof SimplePrincipalCollection){
                //强转
                SimplePrincipalCollection spc = (SimplePrincipalCollection)obj;
                //判断用户,匹配用户ID。
                obj = spc.getPrimaryPrincipal();
                if(null != obj && obj instanceof User){
                    User user = (User) obj;
                    System.out.println("user:"+user);
                    //比较用户ID,符合即加入集合
                    if(null != user && userIds.contains(user.getId())){
                        list.add(spc);
                    }
                }
            }
        }
        RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
        MyShiroRealm realm = (MyShiroRealm)securityManager.getRealms().iterator().next();
        for (SimplePrincipalCollection simplePrincipalCollection : list) {
            realm.clearCachedAuthorizationInfo(simplePrincipalCollection);
        }
    }
}

具体代码不在这里赘述,相关demo见码云:

https://gitee.com/MoCunXiaoPing/springboot-shiro

猜你喜欢

转载自blog.csdn.net/typ1805/article/details/82989216