SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例

来源:互联网 发布:linux cdn服务器搭建 编辑:程序博客网 时间:2024/06/01 07:26

1.前言

本文主要介绍使用SpringBoot与shiro实现基于数据库的细粒度动态权限管理系统实例。 
使用技术:SpringBoot、mybatis、shiro、thymeleaf、pagehelper、Mapper插件、druid、dataTables、ztree、jQuery 
开发工具:intellij idea 
数据库:MySQL、Redis 
基本上是基于使用SpringSecurity的demo上修改而成,地址 http://blog.csdn.net/poorcoder_/article/details/70231779

2.表结构

还是是用标准的5张表来展现权限。如下图:image 
分别为用户表,角色表,资源表,用户角色表,角色资源表。在这个demo中使用了mybatis-generator自动生成代码。运行mybatis-generator:generate -e 根据数据库中的表,生成 相应的model,mapper单表的增删改查。不过如果是导入本项目的就别运行这个命令了。新增表的话,也要修改mybatis-generator-config.xml中的tableName,指定表名再运行。

3.maven配置

<?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.study</groupId>    <artifactId>springboot-shiro</artifactId>    <version>0.0.1-SNAPSHOT</version>    <packaging>jar</packaging>    <name>springboot-shiro</name>    <description>Demo project for Spring Boot</description>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>1.5.2.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </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>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-thymeleaf</artifactId>        </dependency>        <dependency>            <groupId>com.github.pagehelper</groupId>            <artifactId>pagehelper-spring-boot-starter</artifactId>            <version>1.1.0</version>        </dependency>        <dependency>            <groupId>tk.mybatis</groupId>            <artifactId>mapper-spring-boot-starter</artifactId>            <version>1.1.1</version>        </dependency>        <dependency>            <groupId>org.apache.shiro</groupId>            <artifactId>shiro-spring</artifactId>            <version>1.3.2</version>        </dependency>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>druid</artifactId>            <version>1.0.29</version>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>        </dependency>        <dependency>            <groupId>net.sourceforge.nekohtml</groupId>            <artifactId>nekohtml</artifactId>            <version>1.9.22</version>        </dependency>        <dependency>            <groupId>com.github.theborakompanioni</groupId>            <artifactId>thymeleaf-extras-shiro</artifactId>            <version>1.2.1</version>        </dependency>        <dependency>            <groupId>org.crazycake</groupId>            <artifactId>shiro-redis</artifactId>            <version>2.4.2.1-RELEASE</version>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>            <plugin>                <groupId>org.mybatis.generator</groupId>                <artifactId>mybatis-generator-maven-plugin</artifactId>                <version>1.3.5</version>                <configuration>                    <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>                    <overwrite>true</overwrite>                    <verbose>true</verbose>                </configuration>                <dependencies>                    <dependency>                        <groupId>mysql</groupId>                        <artifactId>mysql-connector-java</artifactId>                        <version>${mysql.version}</version>                    </dependency>                    <dependency>                        <groupId>tk.mybatis</groupId>                        <artifactId>mapper</artifactId>                        <version>3.4.0</version>                    </dependency>                </dependencies>            </plugin>        </plugins>    </build></project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120

4.配置Druid

package com.study.config;import com.alibaba.druid.support.http.StatViewServlet;import com.alibaba.druid.support.http.WebStatFilter;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * Created by yangqj on 2017/4/19. */@Configurationpublic class DruidConfig {    @Bean    public ServletRegistrationBean druidServlet() {        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");        //登录查看信息的账号密码.        servletRegistrationBean.addInitParameter("loginUsername","admin");        servletRegistrationBean.addInitParameter("loginPassword","123456");        return servletRegistrationBean;    }    @Bean    public FilterRegistrationBean filterRegistrationBean() {        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();        filterRegistrationBean.setFilter(new WebStatFilter());        filterRegistrationBean.addUrlPatterns("/*");        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");        return filterRegistrationBean;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

在application.properties中加入:

# 数据源基础配置spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/shirospring.datasource.username=rootspring.datasource.password=root# 连接池配置# 初始化大小,最小,最大spring.datasource.initialSize=1spring.datasource.minIdle=1spring.datasource.maxActive=20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

配置好后,运行项目访问http://localhost:8080/druid/ 输入配置的账号密码admin,123456进入:image

5.配置mybatis

使用springboot 整合mybatis非常方便,只需在application.properties

mybatis.type-aliases-package=com.study.modelmybatis.mapper-locations=classpath:mapper/*.xmlmapper.mappers=com.study.util.MyMappermapper.not-empty=falsemapper.identity=MYSQLpagehelper.helperDialect=mysqlpagehelper.reasonable=truepagehelper.supportMethodsArguments=truepagehelper.params=count\=countSql
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

将相应的路径改成项目包所在的路径即可。配置文件中可以看出来还加入了pagehelper 和Mapper插件。如果不需要,把上面配置文件中的 pagehelper删除。

MyMapper:
package com.study.util;/** * Created by yangqj on 2017/4/20. */import tk.mybatis.mapper.common.Mapper;import tk.mybatis.mapper.common.MySqlMapper;public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

对于Springboot整合mybatis可以参考https://github.com/abel533/MyBatis-Spring-Boot

6.thymeleaf配置

thymeleaf是springboot官方推荐的,所以来试一下。 
首先加入配置:

#spring.thymeleaf.prefix=classpath:/templates/#spring.thymeleaf.suffix=.html#spring.thymeleaf.mode=HTML5#spring.thymeleaf.encoding=UTF-8# ;charset=<encoding> is added#spring.thymeleaf.content-type=text/html# set to false for hot refreshspring.thymeleaf.cache=falsespring.thymeleaf.mode=LEGACYHTML5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以看到其实上面都是注释了的,因为springboot会根据约定俗成的方式帮我们配置好。所以上面注释部分是springboot自动配置的,如果需要自定义配置,只需要修改上注释部分即可。 
后两行没有注释的部分,spring.thymeleaf.cache=false表示关闭缓存,这样修改文件后不需要重新启动,缓存默认是开启的,所以指定为false。但是在intellij idea中还需要按Ctrl + Shift + F9. 
对于spring.thymeleaf.mode=LEGACYHTML5。thymeleaf对html中的语法要求非常严格,像我从网上找的模板,使用thymeleaf后报一堆的语法错误,后来没办法,使用弱语法校验,所以加入配置spring.thymeleaf.mode=LEGACYHTML5。加入这个配置后还需要在maven中加入

<dependency>    <groupId>net.sourceforge.nekohtml</groupId>    <artifactId>nekohtml</artifactId>    <version>1.9.22</version></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

否则会报错的。 
在前端页面的头部加入一下配置后,就可以使用thymeleaf了

<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" />
  • 1
  • 1

不过这个项目因为使用了datatables都是使用jquery 的ajax来访问数据与处理数据,所以用到的thymeleaf语法非常少,基本上可以参考的就是js即css的导入和类似于jsp的include功能的部分页面引入。 
对于静态文件的引入:

 <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" />
  • 1
  • 1

而文件在项目中的位置是static-css-bootstrap.min.css。为什么这样可以访问到该文件,也是因为springboot对于静态文件会自动查找/static public、/resources、/META-INF/resources下的文件。所以不需要加static.

页面引入: 
局部页面如下:

<div  th:fragment="top">    ...</div>
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

主体页面映入方式:

<div th:include="common/top :: top"></div>
  • 1
  • 1

inclide=”文件路径::局部代码片段名称”

7.shiro配置

配置文件ShiroConfig
package com.study.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;import com.github.pagehelper.util.StringUtil;import com.study.model.Resources;import com.study.service.ResourcesService;import com.study.shiro.MyShiroRealm;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.mgt.SecurityManager;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 java.util.LinkedHashMap;import java.util.List;import java.util.Map;/** * Created by yangqj on 2017/4/23. */@Configurationpublic class ShiroConfig {    @Autowired(required = false)    private ResourcesService resourcesService;    @Value("${spring.redis.host}")    private String host;    @Value("${spring.redis.port}")    private int port;    @Value("${spring.redis.timeout}")    private int timeout;    @Bean    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {        return new LifecycleBeanPostProcessor();    }    /**     * ShiroDialect,为了在thymeleaf里使用shiro的标签的bean     * @return     */    @Bean    public ShiroDialect shiroDialect() {        return new ShiroDialect();    }    /**     * ShiroFilterFactoryBean 处理拦截资源文件问题。     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager     *     Filter Chain定义说明     1、一个URL可以配置多个Filter,使用逗号分隔     2、当设置多个过滤器时,全部验证通过,才视为通过     3、部分过滤器可指定参数,如perms,roles     *     */    @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中的代码;     * )     * @return     */    @Bean    public HashedCredentialsMatcher hashedCredentialsMatcher(){        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));        return hashedCredentialsMatcher;    }    /**     *  开启shiro aop注解支持.     *  使用代理方式;所以需要开启代码支持;     * @param securityManager     * @return     */    @Bean    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);        return authorizationAttributeSourceAdvisor;    }    /**     * 配置shiro redisManager     * 使用的是shiro-redis开源插件     * @return     */    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开源插件     * @return     */    public RedisCacheManager cacheManager() {        RedisCacheManager redisCacheManager = new RedisCacheManager();        redisCacheManager.setRedisManager(redisManager());        return redisCacheManager;    }    /**     * RedisSessionDAO shiro sessionDao层的实现 通过redis     * 使用的是shiro-redis开源插件     */    @Bean    public RedisSessionDAO redisSessionDAO() {        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();        redisSessionDAO.setRedisManager(redisManager());        return redisSessionDAO;    }    /**     * shiro session的管理     */    @Bean    public DefaultWebSessionManager sessionManager() {        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();        sessionManager.setSessionDAO(redisSessionDAO());        return sessionManager;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
配置自定义Realm
package com.study.shiro;import com.study.model.Resources;import com.study.model.User;import com.study.service.ResourcesService;import com.study.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.realm.AuthorizingRealm;import org.apache.shiro.session.Session;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.util.ByteSource;import javax.annotation.Resource;import java.util.HashMap;import java.util.List;import java.util.Map;/** * Created by yangqj on 2017/4/21. */public class MyShiroRealm extends AuthorizingRealm {    @Resource    private UserService userService;    @Resource    private ResourcesService resourcesService;    //授权    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        User user= (User) SecurityUtils.getSubject().getPrincipal();//User{id=1, username='admin', password='3ef7164d1f6167cb9f2658c07d3c2f0a', enable=1}        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;    }    //认证    @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;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
认证:

shiro的主要模块分别就是授权和认证和会话管理。 
我们先讲认证。认证就是验证用户。比如用户登录的时候验证账号密码是否正确。 
我们可以把对登录的验证交给shiro。我们执行要查询相应的用户信息,并传给shiro。如下代码则为用户登录:

 @RequestMapping(value="/login",method=RequestMethod.POST)    public String login(HttpServletRequest request, User user, Model model){        if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword())) {            request.setAttribute("msg", "用户名或密码不能为空!");            return "login";        }        Subject subject = SecurityUtils.getSubject();        UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());        try {            subject.login(token);            return "redirect:usersPage";        }catch (LockedAccountException lae) {            token.clear();            request.setAttribute("msg", "用户已经被锁定不能登录,请与管理员联系!");            return "login";        } catch (AuthenticationException e) {            token.clear();            request.setAttribute("msg", "用户或密码不正确!");            return "login";        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

可见用户登陆的代码主要就是 subject.login(token);调用后就会进去我们自定义的realm中的doGetAuthenticationInfo()方法。

 //认证    @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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

而我们在ShiroConfig中配置了凭证匹配器:

@Bean    public MyShiroRealm myShiroRealm(){        MyShiroRealm myShiroRealm = new MyShiroRealm();        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());        return myShiroRealm;    } @Bean    public HashedCredentialsMatcher hashedCredentialsMatcher(){        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));        return hashedCredentialsMatcher;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

所以在认证时的密码是加过密的,使用md5散发将密码与盐值组合加密两次。则我们在增加用户的时候,对用户的密码则要进过相同规则的加密才行。 
添加用户代码如下:

@RequestMapping(value = "/add")    public String add(User user) {        User u = userService.selectByUsername(user.getUsername());        if(u != null)            return "error";        try {            user.setEnable(1);            PasswordHelper passwordHelper = new PasswordHelper();            passwordHelper.encryptPassword(user);            userService.save(user);            return "success";        } catch (Exception e) {            e.printStackTrace();            return "fail";        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

PasswordHelper:

package com.study.util;import com.study.model.User;import org.apache.shiro.crypto.RandomNumberGenerator;import org.apache.shiro.crypto.SecureRandomNumberGenerator;import org.apache.shiro.crypto.hash.SimpleHash;import org.apache.shiro.util.ByteSource;public class PasswordHelper {    //private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();    private String algorithmName = "md5";    private int hashIterations = 2;    public void encryptPassword(User user) {        //String salt=randomNumberGenerator.nextBytes().toHex();        String newPassword = new SimpleHash(algorithmName, user.getPassword(),  ByteSource.Util.bytes(user.getUsername()), hashIterations).toHex();        //String newPassword = new SimpleHash(algorithmName, user.getPassword()).toHex();        user.setPassword(newPassword);    }    public static void main(String[] args) {        PasswordHelper passwordHelper = new PasswordHelper();        User user = new User();        user.setUsername("admin");            user.setPassword("admin");        passwordHelper.encryptPassword(user);        System.out.println(user);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
授权:

接下来讲下授权。在自定义relalm中的代码为:

 //授权    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        User user= (User) SecurityUtils.getSubject().getPrincipal();//User{id=1, username='admin', password='3ef7164d1f6167cb9f2658c07d3c2f0a', enable=1}        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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

从以上代码中可以看出来,我根据用户id查询出用户的权限,放入SimpleAuthorizationInfo。关联表user_role,role_resources,resources,三张表,根据用户所拥有的角色,角色所拥有的权限,查询出分配给该用户的所有权限的url。当访问的链接中配置在shiro中时,或者使用shiro标签,shiro权限注解时,则会访问该方法,判断该用户是否拥有相应的权限。

在ShiroConfig中有如下代码:

 @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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

该代码片段为配置shiro的过滤器。以上代码将静态文件设置为任何权限都可访问,然后

 List<Resources> resourcesList = resourcesService.queryAll();         for(Resources resources:resourcesList){            if (StringUtil.isNotEmpty(resources.getResurl())) {                String permission = "perms[" + resources.getResurl()+ "]";                filterChainDefinitionMap.put(resources.getResurl(),permission);            }        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在数据中查询所有的资源,将该资源的url当作key,配置拥有该url权限的用户才可访问该url。 
最后加入 filterChainDefinitionMap.put(“/*”, “authc”);表示其他没有配置的链接都需要认证才可访问。注意这个要放最后面,因为shiro的匹配是从上往下,如果匹配到就不继续匹配了,所以把 /放到最前面,则 后面的链接都无法匹配到了。 
而这段代码是在项目启动的时候加载的。加载的数据是放到内存中的。但是当权限增加或者删除时,正常情况下不会重新启动来,重新加载权限。所以需要调用以下代码的updatePermission()方法来重新加载权限。其实下面的代码有些重复了,可以稍微调整下,我就先这么写了。

package com.study.shiro;import com.github.pagehelper.util.StringUtil;import com.study.model.Resources;import com.study.model.User;import com.study.service.ResourcesService;import org.apache.shiro.SecurityUtils;import org.apache.shiro.mgt.RealmSecurityManager;import org.apache.shiro.session.Session;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.subject.SimplePrincipalCollection;import org.apache.shiro.subject.support.DefaultSubjectContext;import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;import org.apache.shiro.web.servlet.AbstractShiroFilter;import org.crazycake.shiro.RedisSessionDAO;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.*;/** * Created by yangqj on 2017/4/30. */@Servicepublic class ShiroService {    @Autowired    private ShiroFilterFactoryBean shiroFilterFactoryBean;    @Autowired    private ResourcesService resourcesService;    @Autowired    private RedisSessionDAO redisSessionDAO;    /**     * 初始化权限     */    public Map<String, String> loadFilterChainDefinitions() {        // 权限控制map.从数据库获取        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();        filterChainDefinitionMap.put("/logout", "logout");        filterChainDefinitionMap.put("/css/**","anon");        filterChainDefinitionMap.put("/js/**","anon");        filterChainDefinitionMap.put("/img/**","anon");        filterChainDefinitionMap.put("/font-awesome/**","anon");        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");        return filterChainDefinitionMap;    }    /**     * 重新加载权限     */    public void updatePermission() {        synchronized (shiroFilterFactoryBean) {            AbstractShiroFilter shiroFilter = null;            try {                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean                        .getObject();            } catch (Exception e) {                throw new RuntimeException(                        "get ShiroFilter from shiroFilterFactoryBean error!");            }            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter                    .getFilterChainResolver();            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver                    .getFilterChainManager();            // 清空老的权限控制            manager.getFilterChains().clear();            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();            shiroFilterFactoryBean                    .setFilterChainDefinitionMap(loadFilterChainDefinitions());            // 重新构建生成            Map<String, String> chains = shiroFilterFactoryBean                    .getFilterChainDefinitionMap();            for (Map.Entry<String, String> entry : chains.entrySet()) {                String url = entry.getKey();                String chainDefinition = entry.getValue().trim()                        .replace(" ", "");                manager.createChain(url, chainDefinition);            }            System.out.println("更新权限成功!!");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
会话管理

这个例子使用了redis保存session。这样可以实现集群的session共享。在ShiroConfig中有代码:

 @Bean    public SecurityManager securityManager(){        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();        //设置realm.        securityManager.setRealm(myShiroRealm());        // 自定义缓存实现 使用redis        //securityManager.setCacheManager(cacheManager());        // 自定义session管理 使用redis        securityManager.setSessionManager(sessionManager());        return securityManager;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

配置了自定义session,网上已经有大神实现了 使用redis 自定义session管理,直接拿来用,引入包

<dependency>    <groupId>org.crazycake</groupId>    <artifactId>shiro-redis</artifactId>    <version>2.4.2.1-RELEASE</version></dependency>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后再配置:

 /**     * 配置shiro redisManager     * 使用的是shiro-redis开源插件     * @return     */    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开源插件     * @return     */    public RedisCacheManager cacheManager() {        RedisCacheManager redisCacheManager = new RedisCacheManager();        redisCacheManager.setRedisManager(redisManager());        return redisCacheManager;    }    /**     * RedisSessionDAO shiro sessionDao层的实现 通过redis     * 使用的是shiro-redis开源插件     */    @Bean    public RedisSessionDAO redisSessionDAO() {        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();        redisSessionDAO.setRedisManager(redisManager());        return redisSessionDAO;    }    /**     * shiro session的管理     */    @Bean    public DefaultWebSessionManager sessionManager() {        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();        sessionManager.setSessionDAO(redisSessionDAO());        return sessionManager;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

RedisConfig:

package com.study.config;import org.apache.log4j.Logger;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;/** * Created by yangqj on 2017/4/30. */@Configuration@EnableCachingpublic class RedisConfig extends CachingConfigurerSupport {    @Value("${spring.redis.host}")    private String host;    @Value("${spring.redis.port}")    private int port;    @Value("${spring.redis.timeout}")    private int timeout;    @Value("${spring.redis.pool.max-idle}")    private int maxIdle;    @Value("${spring.redis.pool.max-wait}")    private long maxWaitMillis;    @Bean    public JedisPool redisPoolFactory() {        Logger.getLogger(getClass()).info("JedisPool注入成功!!");        Logger.getLogger(getClass()).info("redis地址:" + host + ":" + port);        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();        jedisPoolConfig.setMaxIdle(maxIdle);        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);        JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);        return jedisPool;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

配置文件 application.properties中加入:

#redis# Redis服务器地址spring.redis.host= localhost# Redis服务器连接端口spring.redis.port= 6379# 连接池中的最大空闲连接spring.redis.pool.max-idle= 8# 连接池中的最小空闲连接spring.redis.pool.min-idle= 0# 连接池最大连接数(使用负值表示没有限制)spring.redis.pool.max-active= 8# 连接池最大阻塞等待时间(使用负值表示没有限制)spring.redis.pool.max-wait= -1# 连接超时时间(毫秒)spring.redis.timeout= 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

当然运行的时候要先启动redis。将自己的redis配置在以上配置中。这样session就存在redis中了。 
上面ShiroConfig中的securityManager()方法中,我把

//securityManager.setCacheManager(cacheManager());
  • 1
  • 1

这行代码注了,是这样的,因为每次在需要验证的地方,比如在subject.hasRole(“admin”) 或 subject.isPermitted(“admin”)、@RequiresRoles(“admin”) 、 shiro:hasPermission=”/users/add”的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()。但是以为这些信息不是经常变的,所以有必要进行缓存。把这行代码的注释打开,的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()的返回结果会被redis缓存。但是这里稍微有个小问题,就是在刚修改用户的权限时,无法立即失效。本来我是使用了ShiroService中的clearUserAuthByUserId()想清除当前session存在的用户的权限缓存,但是没有效果。不知道什么原因。希望哪个大神看到后帮忙弄个解决方法。所以我干脆就把doGetAuthorizationInfo()的返回结果通过spring cache的方式加入缓存。

  @Cacheable(cacheNames="resources",key="#map['userid'].toString()+#map['type']")    public List<Resources> loadUserResources(Map<String, Object> map) {        return resourcesMapper.loadUserResources(map);    }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这样也可以实现,然后在修改权限时加上注解

 @CacheEvict(cacheNames="resources", allEntries=true)
  • 1
  • 1

这样修改权限后可以立即生效。其实我感觉这样不好,因为清楚了我是清除了所有用户的权限缓存,其实只要修改当前session在线中被修改权限的用户就行了。 先这样吧,以后再研究下,修改得更好一点。

按钮控制

在前端页面,对按钮进行细粒度权限控制,只需要在按钮上加上shiro:hasPermission

  <button shiro:hasPermission="/users/add" type="button"  onclick="$('#addUser').modal();" class="btn btn-info" >新增</button>
  • 1
  • 1

这里的参数就是我们在ShiroConfig-shirFilter()权限加载时的过滤器 中的value,也就是资源的url。

  filterChainDefinitionMap.put(resources.getResurl(),permission);
  • 1
  • 1

8.效果图

image

image

9.运行、下载

下载项目后运行resources下的shiro.sql文件。需要运行redis后运行项目。访问http://localhost:8080/ 账号密码:admin admin 或user1 user1.新增的用户也可以登录。

github下载地址:https://github.com/lovelyCoder/springboot-shiro

转载请标明出处:http://blog.csdn.net/poorCoder_/article/details/71374002

阅读全文
0 0
原创粉丝点击