zoukankan      html  css  js  c++  java
  • SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例(转)

    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配置

      1 <?xml version="1.0" encoding="UTF-8"?>
      2 
      3 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      4 
      5          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      6 
      7     <modelVersion>4.0.0</modelVersion>
      8 
      9 
     10 
     11     <groupId>com.study</groupId>
     12 
     13     <artifactId>springboot-shiro</artifactId>
     14 
     15     <version>0.0.1-SNAPSHOT</version>
     16 
     17     <packaging>jar</packaging>
     18 
     19 
     20 
     21     <name>springboot-shiro</name>
     22 
     23     <description>Demo project for Spring Boot</description>
     24 
     25 
     26 
     27     <parent>
     28         <groupId>org.springframework.boot</groupId>
     29 
     30         <artifactId>spring-boot-starter-parent</artifactId>
     31 
     32         <version>1.5.2.RELEASE</version>
     33 
     34         <relativePath/> <!-- lookup parent from repository -->
     35 
     36     </parent>
     37 
     38 
     39 
     40     <properties>
     41         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     42 
     43         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     44 
     45         <java.version>1.8</java.version>
     46 
     47     </properties>
     48 
     49 
     50 
     51     <dependencies>
     52 
     53         <dependency>
     54 
     55             <groupId>org.springframework.boot</groupId>
     56 
     57             <artifactId>spring-boot-starter</artifactId>
     58 
     59         </dependency>
     60 
     61 
     62 
     63         <dependency>
     64 
     65             <groupId>org.springframework.boot</groupId>
     66 
     67             <artifactId>spring-boot-starter-test</artifactId>
     68 
     69             <scope>test</scope>
     70 
     71         </dependency>
     72 
     73         <dependency>
     74 
     75             <groupId>org.springframework.boot</groupId>
     76 
     77             <artifactId>spring-boot-starter-web</artifactId>
     78 
     79         </dependency>
     80 
     81         <dependency>
     82 
     83             <groupId>org.springframework.boot</groupId>
     84 
     85             <artifactId>spring-boot-starter-thymeleaf</artifactId>
     86 
     87         </dependency>
     88 
     89         <dependency>
     90 
     91             <groupId>com.github.pagehelper</groupId>
     92 
     93             <artifactId>pagehelper-spring-boot-starter</artifactId>
     94 
     95             <version>1.1.0</version>
     96         </dependency>
     97 
     98         <dependency>
     99 
    100             <groupId>tk.mybatis</groupId>
    101 
    102             <artifactId>mapper-spring-boot-starter</artifactId>
    103 
    104             <version>1.1.1</version>
    105 
    106         </dependency>
    107 
    108         <dependency>
    109 
    110             <groupId>org.apache.shiro</groupId>
    111 
    112             <artifactId>shiro-spring</artifactId>
    113 
    114             <version>1.3.2</version>
    115 
    116         </dependency>
    117 
    118         <dependency>
    119 
    120             <groupId>com.alibaba</groupId>
    121 
    122             <artifactId>druid</artifactId>
    123 
    124             <version>1.0.29</version>
    125 
    126         </dependency>
    127 
    128         <dependency>
    129 
    130             <groupId>mysql</groupId>
    131 
    132             <artifactId>mysql-connector-java</artifactId>
    133 
    134         </dependency>
    135 
    136         <dependency>
    137 
    138             <groupId>net.sourceforge.nekohtml</groupId>
    139 
    140             <artifactId>nekohtml</artifactId>
    141 
    142             <version>1.9.22</version>
    143 
    144         </dependency>
    145 
    146         <dependency>
    147 
    148             <groupId>com.github.theborakompanioni</groupId>
    149 
    150             <artifactId>thymeleaf-extras-shiro</artifactId>
    151 
    152             <version>1.2.1</version>
    153 
    154         </dependency>
    155 
    156         <dependency>
    157 
    158             <groupId>org.crazycake</groupId>
    159 
    160             <artifactId>shiro-redis</artifactId>
    161 
    162             <version>2.4.2.1-RELEASE</version>
    163 
    164         </dependency>
    165 
    166     </dependencies>
    167 
    168 
    169     <build>
    170 
    171         <plugins>
    172 
    173             <plugin>
    174 
    175                 <groupId>org.springframework.boot</groupId>
    176 
    177                 <artifactId>spring-boot-maven-plugin</artifactId>
    178 
    179             </plugin>
    180 
    181             <plugin>
    182 
    183                 <groupId>org.mybatis.generator</groupId>
    184 
    185                 <artifactId>mybatis-generator-maven-plugin</artifactId>
    186 
    187                 <version>1.3.5</version>
    188 
    189                 <configuration>
    190 
    191                     <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
    192 
    193                     <overwrite>true</overwrite>
    194 
    195                     <verbose>true</verbose>
    196 
    197                 </configuration>
    198 
    199                 <dependencies>
    200 
    201                     <dependency>
    202 
    203                         <groupId>mysql</groupId>
    204 
    205                         <artifactId>mysql-connector-java</artifactId>
    206 
    207                         <version>${mysql.version}</version>
    208 
    209                     </dependency>
    210 
    211                     <dependency>
    212 
    213                         <groupId>tk.mybatis</groupId>
    214 
    215                         <artifactId>mapper</artifactId>
    216 
    217                         <version>3.4.0</version>
    218 
    219                     </dependency>
    220 
    221                 </dependencies>
    222 
    223             </plugin>
    224 
    225         </plugins>
    226 
    227     </build>
    228 
    229 
    230 
    231 
    232 
    233 </project>

    4.配置Druid

     1 package com.study.config;
     2 
     3 
     4 
     5 import com.alibaba.druid.support.http.StatViewServlet;
     6 
     7 import com.alibaba.druid.support.http.WebStatFilter;
     8 
     9 import org.springframework.boot.web.servlet.FilterRegistrationBean;
    10 
    11 import org.springframework.boot.web.servlet.ServletRegistrationBean;
    12 
    13 import org.springframework.context.annotation.Bean;
    14 
    15 import org.springframework.context.annotation.Configuration;
    16 
    17 
    18 
    19 
    20 /**
    21 
    22  * Created by yangqj on 2017/4/19.
    23 
    24  */
    25 
    26 @Configuration
    27 
    28 public class DruidConfig {
    29 
    30 
    31     @Bean
    32 
    33     public ServletRegistrationBean druidServlet() {
    34 
    35 
    36 
    37         ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
    38 
    39         //登录查看信息的账号密码.
    40 
    41 
    42 
    43         servletRegistrationBean.addInitParameter("loginUsername","admin");
    44 
    45 
    46         servletRegistrationBean.addInitParameter("loginPassword","123456");
    47 
    48         return servletRegistrationBean;
    49 
    50     }
    51 
    52 
    53     @Bean
    54 
    55     public FilterRegistrationBean filterRegistrationBean() {
    56 
    57         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    58 
    59         filterRegistrationBean.setFilter(new WebStatFilter());
    60 
    61         filterRegistrationBean.addUrlPatterns("/*");
    62 
    63         filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    64 
    65         return filterRegistrationBean;
    66 
    67     }
    68 
    69 }

    在application.properties中加入:

     1 # 数据源基础配置
     2 
     3 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
     4 
     5 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
     6 
     7 spring.datasource.url=jdbc:mysql://localhost:3306/shiro
     8 
     9 spring.datasource.username=root
    10 
    11 spring.datasource.password=root
    12 
    13 # 连接池配置
    14 
    15 # 初始化大小,最小,最大
    16 
    17 spring.datasource.initialSize=1
    18 
    19 spring.datasource.minIdle=1
    20 
    21 spring.datasource.maxActive=20
    配置好后,运行项目访问http://localhost:8080/druid/ 输入配置的账号密码admin,123456进入

    
    
    

    5.配置mybatis

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

     1 mybatis.type-aliases-package=com.study.model
     2 
     3 mybatis.mapper-locations=classpath:mapper/*.xml
     4 
     5 mapper.mappers=com.study.util.MyMapper
     6 
     7 mapper.not-empty=false
     8 
     9 mapper.identity=MYSQL
    10 
    11 pagehelper.helperDialect=mysql
    12 
    13 pagehelper.reasonable=true
    14 
    15 pagehelper.supportMethodsArguments=true
    16 
    17 
    18 pagehelper.params=count=countSql

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

    MyMapper:
     1 package com.study.util;
     2 
     3 
     4 
     5 /**
     6 
     7  * Created by yangqj on 2017/4/20.
     8 
     9  */
    10 
    11 import tk.mybatis.mapper.common.Mapper;
    12 
    13 import tk.mybatis.mapper.common.MySqlMapper;
    14 
    15 public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
    16 
    17 }
    对于Springboot整合mybatis可以参考https://github.com/abel533/MyBatis-Spring-Boot

    6.thymeleaf配置

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


     1 #spring.thymeleaf.prefix=classpath:/templates/
     2 
     3 #spring.thymeleaf.suffix=.html
     4 
     5 #spring.thymeleaf.mode=HTML5
     6 
     7 #spring.thymeleaf.encoding=UTF-8
     8 
     9 # ;charset=<encoding> is added
    10 
    11 #spring.thymeleaf.content-type=text/html
    12 
    13 # set to false for hot refresh
    14 
    15 spring.thymeleaf.cache=false
    16 
    17 spring.thymeleaf.mode=LEGACYHTML5
    可以看到其实上面都是注释了的,因为springboot会根据约定俗成的方式帮我们配置好。所以上面注释部分是springboot自动配置的,如果需要自定义配置,只需要修改上注释部分即可。 
    后两行没有注释的部分,spring.thymeleaf.cache=false表示关闭缓存,这样修改文件后不需要重新启动,缓存默认是开启的,所以指定为false。但是在intellij idea中还需要按Ctrl + Shift + F9.
    对于spring.thymeleaf.mode=LEGACYHTML5。thymeleaf对html中的语法要求非常严格,像我从网上找的模板,使用thymeleaf后报一堆的语法错误,后来没办法,使用弱语法校验,所以加入配置spring.thymeleaf.mode=LEGACYHTML5。加入这个配置后还需要在maven中加入
    1 <dependency>
    2 
    3     <groupId>net.sourceforge.nekohtml</groupId>
    4 
    5     <artifactId>nekohtml</artifactId>
    6 
    7     <version>1.9.22</version>
    8 
    9 </dependency>
    否则会报错的。 
    在前端页面的头部加入一下配置后,就可以使用thymeleaf了


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



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


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

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

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

    1 <div  th:fragment="top">
    2     ...
    3 </div>
    主体页面映入方式:


    1 <div th:include="common/top :: top"></div>
    inclide=”文件路径::局部代码片段名称”

    7.shiro配置

    配置文件ShiroConfig
      1 package com.study.config;
      2 
      3 
      4 import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
      5 
      6 import com.github.pagehelper.util.StringUtil;
      7 
      8 import com.study.model.Resources;
      9 
     10 import com.study.service.ResourcesService;
     11 
     12 import com.study.shiro.MyShiroRealm;
     13 
     14 import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
     15 
     16 import org.apache.shiro.mgt.SecurityManager;
     17 
     18 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
     19 
     20 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
     21 
     22 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
     23 
     24 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
     25 
     26 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
     27 
     28 import org.crazycake.shiro.RedisCacheManager;
     29 
     30 import org.crazycake.shiro.RedisManager;
     31 
     32 import org.crazycake.shiro.RedisSessionDAO;
     33 
     34 import org.springframework.beans.factory.annotation.Autowired;
     35 
     36 import org.springframework.beans.factory.annotation.Value;
     37 
     38 import org.springframework.context.annotation.Bean;
     39 
     40 import org.springframework.context.annotation.Configuration;
     41 
     42 
     43 
     44 import java.util.LinkedHashMap;
     45 
     46 import java.util.List;
     47 
     48 import java.util.Map;
     49 
     50 
     51 
     52 /**
     53 
     54  * Created by yangqj on 2017/4/23.
     55 
     56  */
     57 
     58 @Configuration
     59 
     60 public class ShiroConfig {
     61 
     62     @Autowired(required = false)
     63 
     64     private ResourcesService resourcesService;
     65 
     66 
     67 
     68     @Value("${spring.redis.host}")
     69 
     70     private String host;
     71 
     72 
     73 
     74     @Value("${spring.redis.port}")
     75 
     76     private int port;
     77 
     78 
     79 
     80     @Value("${spring.redis.timeout}")
     81 
     82     private int timeout;
     83 
     84 
     85 
     86     @Bean
     87 
     88     public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
     89 
     90         return new LifecycleBeanPostProcessor();
     91 
     92     }
     93 
     94 
     95     /**
     96 
     97      * ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
     98 
     99      * @return
    100 
    101      */
    102 
    103     @Bean
    104 
    105     public ShiroDialect shiroDialect() {
    106 
    107         return new ShiroDialect();
    108 
    109     }
    110 
    111     /**
    112 
    113      * ShiroFilterFactoryBean 处理拦截资源文件问题。
    114 
    115      * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
    116 
    117      * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
    118 
    119      *
    120 
    121      Filter Chain定义说明
    122 
    123      1、一个URL可以配置多个Filter,使用逗号分隔
    124 
    125      2、当设置多个过滤器时,全部验证通过,才视为通过
    126 
    127      3、部分过滤器可指定参数,如perms,roles
    128 
    129      *
    130 
    131      */
    132 
    133     @Bean
    134 
    135     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
    136 
    137         System.out.println("ShiroConfiguration.shirFilter()");
    138 
    139         ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
    140 
    141 
    142 
    143         // 必须设置 SecurityManager
    144 
    145         shiroFilterFactoryBean.setSecurityManager(securityManager);
    146 
    147         // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
    148 
    149         shiroFilterFactoryBean.setLoginUrl("/login");
    150 
    151         // 登录成功后要跳转的链接
    152 
    153         shiroFilterFactoryBean.setSuccessUrl("/usersPage");
    154 
    155         //未授权界面;
    156 
    157         shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    158 
    159         //拦截器.
    160 
    161         Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
    162 
    163 
    164 
    165         //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
    166 
    167         filterChainDefinitionMap.put("/logout", "logout");
    168 
    169         filterChainDefinitionMap.put("/css/**","anon");
    170 
    171         filterChainDefinitionMap.put("/js/**","anon");
    172 
    173         filterChainDefinitionMap.put("/img/**","anon");
    174 
    175         filterChainDefinitionMap.put("/font-awesome/**","anon");
    176 
    177         //<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
    178 
    179         //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
    180 
    181         //自定义加载权限资源关系
    182 
    183         List<Resources> resourcesList = resourcesService.queryAll();
    184 
    185          for(Resources resources:resourcesList){
    186 
    187 
    188 
    189             if (StringUtil.isNotEmpty(resources.getResurl())) {
    190 
    191                 String permission = "perms[" + resources.getResurl()+ "]";
    192 
    193                 filterChainDefinitionMap.put(resources.getResurl(),permission);
    194 
    195             }
    196 
    197         }
    198 
    199         filterChainDefinitionMap.put("/**", "authc");
    200 
    201 
    202 
    203 
    204         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    205 
    206         return shiroFilterFactoryBean;
    207 
    208     }
    209 
    210 
    211 
    212     @Bean
    213 
    214     public SecurityManager securityManager(){
    215 
    216         DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
    217 
    218         //设置realm.
    219 
    220         securityManager.setRealm(myShiroRealm());
    221 
    222         // 自定义缓存实现 使用redis
    223 
    224         //securityManager.setCacheManager(cacheManager());
    225 
    226         // 自定义session管理 使用redis
    227 
    228         securityManager.setSessionManager(sessionManager());
    229 
    230         return securityManager;
    231 
    232     }
    233 
    234 
    235 
    236     @Bean
    237 
    238     public MyShiroRealm myShiroRealm(){
    239 
    240         MyShiroRealm myShiroRealm = new MyShiroRealm();
    241 
    242         myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    243 
    244         return myShiroRealm;
    245 
    246     }
    247 
    248 
    249 
    250     /**
    251 
    252      * 凭证匹配器
    253      * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
    254 
    255      *  所以我们需要修改下doGetAuthenticationInfo中的代码;
    256 
    257      * )
    258 
    259      * @return
    260 
    261      */
    262 
    263     @Bean
    264 
    265     public HashedCredentialsMatcher hashedCredentialsMatcher(){
    266 
    267         HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    268 
    269 
    270 
    271         hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
    272 
    273         hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
    274 
    275 
    276 
    277         return hashedCredentialsMatcher;
    278 
    279     }
    280 
    281 
    282 
    283     /**
    284 
    285      *  开启shiro aop注解支持.
    286 
    287      *  使用代理方式;所以需要开启代码支持;
    288 
    289      * @param securityManager
    290      * @return
    291 
    292      */
    293 
    294     @Bean
    295 
    296     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
    297 
    298         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    299 
    300         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    301 
    302         return authorizationAttributeSourceAdvisor;
    303 
    304     }
    305 
    306 
    307     /**
    308 
    309      * 配置shiro redisManager
    310 
    311      * 使用的是shiro-redis开源插件
    312 
    313      * @return
    314 
    315      */
    316 
    317     public RedisManager redisManager() {
    318 
    319         RedisManager redisManager = new RedisManager();
    320 
    321         redisManager.setHost(host);
    322 
    323         redisManager.setPort(port);
    324 
    325         redisManager.setExpire(1800);// 配置缓存过期时间
    326 
    327         redisManager.setTimeout(timeout);
    328 
    329         // redisManager.setPassword(password);
    330 
    331         return redisManager;
    332 
    333     }
    334 
    335 
    336 
    337     /**
    338 
    339      * cacheManager 缓存 redis实现
    340 
    341      * 使用的是shiro-redis开源插件
    342 
    343      * @return
    344 
    345      */
    346 
    347     public RedisCacheManager cacheManager() {
    348 
    349         RedisCacheManager redisCacheManager = new RedisCacheManager();
    350 
    351         redisCacheManager.setRedisManager(redisManager());
    352 
    353         return redisCacheManager;
    354 
    355     }
    356 
    357 
    358 
    359     /**
    360 
    361      * RedisSessionDAO shiro sessionDao层的实现 通过redis
    362 
    363      * 使用的是shiro-redis开源插件
    364 
    365      */
    366 
    367     @Bean
    368 
    369     public RedisSessionDAO redisSessionDAO() {
    370 
    371         RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
    372 
    373         redisSessionDAO.setRedisManager(redisManager());
    374 
    375         return redisSessionDAO;
    376 
    377     }
    378 
    379 
    380     /**
    381 
    382      * shiro session的管理
    383 
    384      */
    385 
    386     @Bean
    387 
    388     public DefaultWebSessionManager sessionManager() {
    389 
    390         DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    391 
    392         sessionManager.setSessionDAO(redisSessionDAO());
    393 
    394         return sessionManager;
    395 
    396     }
    397 
    398 
    399 }
    配置自定义Realm
      1 package com.study.shiro;
      2 
      3 
      4 import com.study.model.Resources;
      5 
      6 import com.study.model.User;
      7 
      8 import com.study.service.ResourcesService;
      9 
     10 import com.study.service.UserService;
     11 
     12 import org.apache.shiro.SecurityUtils;
     13 
     14 import org.apache.shiro.authc.*;
     15 
     16 import org.apache.shiro.authz.AuthorizationInfo;
     17 
     18 import org.apache.shiro.authz.SimpleAuthorizationInfo;
     19 
     20 import org.apache.shiro.realm.AuthorizingRealm;
     21 
     22 import org.apache.shiro.session.Session;
     23 
     24 import org.apache.shiro.subject.PrincipalCollection;
     25 
     26 import org.apache.shiro.util.ByteSource;
     27 
     28 
     29 
     30 import javax.annotation.Resource;
     31 
     32 import java.util.HashMap;
     33 
     34 import java.util.List;
     35 
     36 import java.util.Map;
     37 
     38 
     39 /**
     40 
     41  * Created by yangqj on 2017/4/21.
     42 
     43  */
     44 
     45 public class MyShiroRealm extends AuthorizingRealm {
     46 
     47 
     48 
     49     @Resource
     50 
     51     private UserService userService;
     52 
     53 
     54 
     55     @Resource
     56 
     57     private ResourcesService resourcesService;
     58 
     59 
     60     //授权
     61 
     62     @Override
     63 
     64     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
     65 
     66         User user= (User) SecurityUtils.getSubject().getPrincipal();//User{id=1, username='admin', password='3ef7164d1f6167cb9f2658c07d3c2f0a', enable=1}
     67 
     68         Map<String,Object> map = new HashMap<String,Object>();
     69 
     70         map.put("userid",user.getId());
     71 
     72         List<Resources> resourcesList = resourcesService.loadUserResources(map);
     73 
     74         // 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
     75 
     76         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
     77 
     78         for(Resources resources: resourcesList){
     79 
     80             info.addStringPermission(resources.getResurl());
     81 
     82         }
     83 
     84         return info;
     85 
     86     }
     87 
     88 
     89     //认证
     90 
     91     @Override
     92 
     93     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
     94 
     95         //获取用户的输入的账号.
     96 
     97         String username = (String)token.getPrincipal();
     98 
     99         User user = userService.selectByUsername(username);
    100 
    101         if(user==null) throw new UnknownAccountException();
    102 
    103         if (0==user.getEnable()) {
    104 
    105             throw new LockedAccountException(); // 帐号锁定
    106 
    107         }
    108 
    109         SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
    110 
    111                 user, //用户
    112 
    113                 user.getPassword(), //密码
    114 
    115                 ByteSource.Util.bytes(username),
    116 
    117                 getName()  //realm name
    118 
    119         );
    120 
    121         // 当验证都通过后,把用户信息放在session里
    122 
    123         Session session = SecurityUtils.getSubject().getSession();
    124 
    125         session.setAttribute("userSession", user);
    126 
    127         session.setAttribute("userSessionId", user.getId());
    128 
    129         return authenticationInfo;
    130 
    131     }
    132 
    133 
    134 
    135 
    136 }
    认证:

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

     1 @RequestMapping(value="/login",method=RequestMethod.POST)
     2 
     3     public String login(HttpServletRequest request, User user, Model model){
     4 
     5         if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword())) {
     6 
     7             request.setAttribute("msg", "用户名或密码不能为空!");
     8 
     9             return "login";
    10 
    11         }
    12 
    13         Subject subject = SecurityUtils.getSubject();
    14 
    15         UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());
    16 
    17         try {
    18 
    19             subject.login(token);
    20 
    21             return "redirect:usersPage";
    22 
    23         }catch (LockedAccountException lae) {
    24 
    25             token.clear();
    26 
    27             request.setAttribute("msg", "用户已经被锁定不能登录,请与管理员联系!");
    28 
    29             return "login";
    30 
    31         } catch (AuthenticationException e) {
    32 
    33             token.clear();
    34 
    35             request.setAttribute("msg", "用户或密码不正确!");
    36 
    37             return "login";
    38 
    39         }
    可见用户登陆的代码主要就是  subject.login(token);调用后就会进去我们自定义的realm中的doGetAuthenticationInfo()方法。
     1 //认证
     2 
     3     @Override
     4 
     5     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
     6 
     7         //获取用户的输入的账号.
     8 
     9         String username = (String)token.getPrincipal();
    10 
    11         User user = userService.selectByUsername(username);
    12 
    13         if(user==null) throw new UnknownAccountException();
    14 
    15         if (0==user.getEnable()) {
    16 
    17             throw new LockedAccountException(); // 帐号锁定
    18 
    19         }
    20 
    21         SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
    22 
    23                 user, //用户
    24 
    25                 user.getPassword(), //密码
    26 
    27                 ByteSource.Util.bytes(username),
    28 
    29                 getName()  //realm name
    30 
    31         );
    32 
    33         // 当验证都通过后,把用户信息放在session里
    34 
    35         Session session = SecurityUtils.getSubject().getSession();
    36 
    37         session.setAttribute("userSession", user);
    38 
    39         session.setAttribute("userSessionId", user.getId());
    40         return authenticationInfo;
    41 
    42     }
    而我们在ShiroConfig中配置了凭证匹配器:

     1 @Bean
     2 
     3     public MyShiroRealm myShiroRealm(){
     4 
     5         MyShiroRealm myShiroRealm = new MyShiroRealm();
     6 
     7         myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
     8 
     9         return myShiroRealm;
    10 
    11     }
    12 
    13 
    14 
    15  @Bean
    16     public HashedCredentialsMatcher hashedCredentialsMatcher(){
    17 
    18         HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    19 
    20 
    21 
    22         hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
    23 
    24         hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
    25 
    26 
    27         return hashedCredentialsMatcher;
    28 
    29     }
    所以在认证时的密码是加过密的,使用md5散发将密码与盐值组合加密两次。则我们在增加用户的时候,对用户的密码则要进过相同规则的加密才行。 
    添加用户代码如下:
     1 @RequestMapping(value = "/add")
     2 
     3     public String add(User user) {
     4 
     5         User u = userService.selectByUsername(user.getUsername());
     6 
     7         if(u != null)
     8 
     9             return "error";
    10 
    11         try {
    12 
    13             user.setEnable(1);
    14 
    15             PasswordHelper passwordHelper = new PasswordHelper();
    16 
    17             passwordHelper.encryptPassword(user);
    18 
    19             userService.save(user);
    20 
    21             return "success";
    22 
    23         } catch (Exception e) {
    24 
    25             e.printStackTrace();
    26 
    27             return "fail";
    28 
    29         }
    30 
    31     }
    PasswordHelper:

     1 package com.study.util;
     2 
     3 
     4 
     5 import com.study.model.User;
     6 
     7 import org.apache.shiro.crypto.RandomNumberGenerator;
     8 
     9 import org.apache.shiro.crypto.SecureRandomNumberGenerator;
    10 
    11 import org.apache.shiro.crypto.hash.SimpleHash;
    12 import org.apache.shiro.util.ByteSource;
    13 
    14 
    15 public class PasswordHelper {
    16 
    17     //private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
    18 
    19     private String algorithmName = "md5";
    20 
    21     private int hashIterations = 2;
    22 
    23 
    24 
    25     public void encryptPassword(User user) {
    26 
    27         //String salt=randomNumberGenerator.nextBytes().toHex();
    28 
    29         String newPassword = new SimpleHash(algorithmName, user.getPassword(),  ByteSource.Util.bytes(user.getUsername()), hashIterations).toHex();
    30 
    31         //String newPassword = new SimpleHash(algorithmName, user.getPassword()).toHex();
    32 
    33         user.setPassword(newPassword);
    34 
    35 
    36 
    37     }
    38 
    39     public static void main(String[] args) {
    40 
    41         PasswordHelper passwordHelper = new PasswordHelper();
    42 
    43         User user = new User();
    44 
    45         user.setUsername("admin");
    46 
    47             user.setPassword("admin");
    48 
    49         passwordHelper.encryptPassword(user);
    50 
    51         System.out.println(user);
    52 
    53     }
    54 
    55 }
    授权:

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

     1  //授权
     2 
     3     @Override
     4 
     5     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
     6 
     7         User user= (User) SecurityUtils.getSubject().getPrincipal();//User{id=1, username='admin', password='3ef7164d1f6167cb9f2658c07d3c2f0a', enable=1}
     8 
     9         Map<String,Object> map = new HashMap<String,Object>();
    10 
    11         map.put("userid",user.getId());
    12 
    13         List<Resources> resourcesList = resourcesService.loadUserResources(map);
    14         // 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
    15 
    16         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    17 
    18         for(Resources resources: resourcesList){
    19 
    20             info.addStringPermission(resources.getResurl());
    21 
    22         }
    23 
    24         return info;
    25 
    26     }

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

    ShiroConfig中有如下代码:

     1 @Bean
     2 
     3     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
     4 
     5         System.out.println("ShiroConfiguration.shirFilter()");
     6 
     7         ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
     8 
     9 
    10 
    11         // 必须设置 SecurityManager
    12 
    13         shiroFilterFactoryBean.setSecurityManager(securityManager);
    14 
    15         // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
    16 
    17         shiroFilterFactoryBean.setLoginUrl("/login");
    18 
    19         // 登录成功后要跳转的链接
    20 
    21         shiroFilterFactoryBean.setSuccessUrl("/usersPage");
    22 
    23         //未授权界面;
    24 
    25         shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    26 
    27         //拦截器.
    28 
    29         Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
    30 
    31 
    32         //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
    33 
    34         filterChainDefinitionMap.put("/logout", "logout");
    35 
    36         filterChainDefinitionMap.put("/css/**","anon");
    37 
    38         filterChainDefinitionMap.put("/js/**","anon");
    39 
    40         filterChainDefinitionMap.put("/img/**","anon");
    41 
    42         filterChainDefinitionMap.put("/font-awesome/**","anon");
    43 
    44         //<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
    45 
    46         //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
    47 
    48         //自定义加载权限资源关系
    49 
    50         List<Resources> resourcesList = resourcesService.queryAll();
    51          for(Resources resources:resourcesList){
    52 
    53 
    54 
    55             if (StringUtil.isNotEmpty(resources.getResurl())) {
    56 
    57                 String permission = "perms[" + resources.getResurl()+ "]";
    58 
    59                 filterChainDefinitionMap.put(resources.getResurl(),permission);
    60 
    61             }
    62 
    63         }
    64 
    65         filterChainDefinitionMap.put("/**", "authc");
    66 
    67 
    68 
    69 
    70         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    71 
    72         return shiroFilterFactoryBean;
    73 
    74     }
    该代码片段为配置shiro的过滤器。以上代码将静态文件设置为任何权限都可访问,然后
     1 List<Resources> resourcesList = resourcesService.queryAll();
     2 
     3          for(Resources resources:resourcesList){
     4 
     5 
     6 
     7             if (StringUtil.isNotEmpty(resources.getResurl())) {
     8                 String permission = "perms[" + resources.getResurl()+ "]";
     9 
    10                 filterChainDefinitionMap.put(resources.getResurl(),permission);
    11 
    12             }
    13 
    14         }
    在数据中查询所有的资源,将该资源的url当作key,配置拥有该url权限的用户才可访问该url。 
    最后加入 filterChainDefinitionMap.put(“/*”, “authc”);表示其他没有配置的链接都需要认证才可访问。注意这个要放最后面,因为shiro的匹配是从上往下,如果匹配到就不继续匹配了,所以把 /放到最前面,则 后面的链接都无法匹配到了。
    而这段代码是在项目启动的时候加载的。加载的数据是放到内存中的。但是当权限增加或者删除时,正常情况下不会重新启动来,重新加载权限。所以需要调用以下代码的updatePermission()方法来重新加载权限。其实下面的代码有些重复了,可以稍微调整下,我就先这么写了。



      1 package com.study.shiro;
      2 
      3 
      4 import com.github.pagehelper.util.StringUtil;
      5 
      6 import com.study.model.Resources;
      7 
      8 import com.study.model.User;
      9 
     10 import com.study.service.ResourcesService;
     11 
     12 import org.apache.shiro.SecurityUtils;
     13 
     14 import org.apache.shiro.mgt.RealmSecurityManager;
     15 
     16 import org.apache.shiro.session.Session;
     17 
     18 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
     19 
     20 import org.apache.shiro.subject.SimplePrincipalCollection;
     21 
     22 import org.apache.shiro.subject.support.DefaultSubjectContext;
     23 
     24 import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
     25 
     26 import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
     27 
     28 import org.apache.shiro.web.servlet.AbstractShiroFilter;
     29 import org.crazycake.shiro.RedisSessionDAO;
     30 
     31 import org.springframework.beans.factory.annotation.Autowired;
     32 
     33 import org.springframework.stereotype.Service;
     34 
     35 
     36 
     37 import java.util.*;
     38 
     39 
     40 /**
     41 
     42  * Created by yangqj on 2017/4/30.
     43 
     44  */
     45 
     46 @Service
     47 
     48 public class ShiroService {
     49 
     50     @Autowired
     51 
     52     private ShiroFilterFactoryBean shiroFilterFactoryBean;
     53 
     54     @Autowired
     55 
     56     private ResourcesService resourcesService;
     57 
     58     @Autowired
     59 
     60     private RedisSessionDAO redisSessionDAO;
     61 
     62     /**
     63 
     64      * 初始化权限
     65 
     66      */
     67 
     68     public Map<String, String> loadFilterChainDefinitions() {
     69 
     70         // 权限控制map.从数据库获取
     71 
     72         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
     73 
     74         filterChainDefinitionMap.put("/logout", "logout");
     75 
     76         filterChainDefinitionMap.put("/css/**","anon");
     77 
     78         filterChainDefinitionMap.put("/js/**","anon");
     79 
     80         filterChainDefinitionMap.put("/img/**","anon");
     81 
     82         filterChainDefinitionMap.put("/font-awesome/**","anon");
     83 
     84         List<Resources> resourcesList = resourcesService.queryAll();
     85 
     86         for(Resources resources:resourcesList){
     87 
     88 
     89 
     90             if (StringUtil.isNotEmpty(resources.getResurl())) {
     91 
     92                 String permission = "perms[" + resources.getResurl()+ "]";
     93 
     94                 filterChainDefinitionMap.put(resources.getResurl(),permission);
     95 
     96             }
     97 
     98         }
     99 
    100         filterChainDefinitionMap.put("/**", "authc");
    101 
    102         return filterChainDefinitionMap;
    103 
    104     }
    105 
    106 
    107     /**
    108      * 重新加载权限
    109      */
    110 
    111     public void updatePermission() {
    112 
    113 
    114 
    115         synchronized (shiroFilterFactoryBean) {
    116 
    117 
    118 
    119             AbstractShiroFilter shiroFilter = null;
    120 
    121             try {
    122 
    123                 shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean
    124 
    125                         .getObject();
    126 
    127             } catch (Exception e) {
    128 
    129                 throw new RuntimeException(
    130 
    131                         "get ShiroFilter from shiroFilterFactoryBean error!");
    132 
    133             }
    134 
    135 
    136 
    137             PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
    138 
    139                     .getFilterChainResolver();
    140 
    141             DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver
    142 
    143                     .getFilterChainManager();
    144 
    145 
    146 
    147             // 清空老的权限控制
    148 
    149             manager.getFilterChains().clear();
    150 
    151 
    152 
    153             shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
    154 
    155             shiroFilterFactoryBean
    156 
    157                     .setFilterChainDefinitionMap(loadFilterChainDefinitions());
    158 
    159             // 重新构建生成
    160 
    161             Map<String, String> chains = shiroFilterFactoryBean
    162 
    163                     .getFilterChainDefinitionMap();
    164 
    165             for (Map.Entry<String, String> entry : chains.entrySet()) {
    166 
    167                 String url = entry.getKey();
    168 
    169                 String chainDefinition = entry.getValue().trim()
    170 
    171                         .replace(" ", "");
    172 
    173                 manager.createChain(url, chainDefinition);
    174 
    175             }
    176 
    177 
    178 
    179             System.out.println("更新权限成功!!");
    180 
    181         }
    182 
    183     }
    184 
    185 
    186 
    187 
    188 }
    会话管理

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

     1 @Bean
     2 
     3     public SecurityManager securityManager(){
     4 
     5         DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
     6 
     7         //设置realm.
     8 
     9         securityManager.setRealm(myShiroRealm());
    10 
    11         // 自定义缓存实现 使用redis
    12 
    13         //securityManager.setCacheManager(cacheManager());
    14 
    15         // 自定义session管理 使用redis
    16 
    17         securityManager.setSessionManager(sessionManager());
    18 
    19         return securityManager;
    20 
    21     }
    配置了自定义session,网上已经有大神实现了 使用redis 自定义session管理,直接拿来用,引入包
    1 <dependency>
    2 
    3     <groupId>org.crazycake</groupId>
    4 
    5     <artifactId>shiro-redis</artifactId>
    6 
    7     <version>2.4.2.1-RELEASE</version>
    8 
    9 </dependency>  
    然后再配置:
     1 /**
     2 
     3      * 配置shiro redisManager
     4 
     5      * 使用的是shiro-redis开源插件
     6 
     7      * @return
     8 
     9      */
    10 
    11     public RedisManager redisManager() {
    12 
    13         RedisManager redisManager = new RedisManager();
    14 
    15         redisManager.setHost(host);
    16 
    17         redisManager.setPort(port);
    18 
    19         redisManager.setExpire(1800);// 配置缓存过期时间
    20 
    21         redisManager.setTimeout(timeout);
    22 
    23         // redisManager.setPassword(password);
    24 
    25         return redisManager;
    26 
    27     }
    28 
    29 
    30     /**
    31 
    32      * cacheManager 缓存 redis实现
    33 
    34      * 使用的是shiro-redis开源插件
    35 
    36      * @return
    37 
    38      */
    39 
    40     public RedisCacheManager cacheManager() {
    41 
    42         RedisCacheManager redisCacheManager = new RedisCacheManager();
    43 
    44         redisCacheManager.setRedisManager(redisManager());
    45 
    46         return redisCacheManager;
    47 
    48     }
    49 
    50 
    51 
    52 
    53     /**
    54 
    55      * RedisSessionDAO shiro sessionDao层的实现 通过redis
    56 
    57      * 使用的是shiro-redis开源插件
    58 
    59      */
    60 
    61     @Bean
    62 
    63     public RedisSessionDAO redisSessionDAO() {
    64 
    65         RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
    66 
    67         redisSessionDAO.setRedisManager(redisManager());
    68 
    69         return redisSessionDAO;
    70 
    71     }
    72 
    73 
    74     /**
    75 
    76      * shiro session的管理
    77 
    78      */
    79 
    80     @Bean
    81 
    82     public DefaultWebSessionManager sessionManager() {
    83 
    84         DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    85 
    86         sessionManager.setSessionDAO(redisSessionDAO());
    87 
    88         return sessionManager;
    89 
    90     }
    RedisConfig

     1 package com.study.config;
     2 
     3 
     4 import org.apache.log4j.Logger;
     5 
     6 import org.springframework.beans.factory.annotation.Value;
     7 
     8 import org.springframework.cache.annotation.CachingConfigurerSupport;
     9 
    10 import org.springframework.cache.annotation.EnableCaching;
    11 
    12 import org.springframework.context.annotation.Bean;
    13 
    14 import org.springframework.context.annotation.Configuration;
    15 
    16 import redis.clients.jedis.JedisPool;
    17 
    18 import redis.clients.jedis.JedisPoolConfig;
    19 
    20 
    21 /**
    22 
    23  * Created by yangqj on 2017/4/30.
    24  */
    25 @Configuration
    26 
    27 @EnableCaching
    28 
    29 public class RedisConfig extends CachingConfigurerSupport {
    30     @Value("${spring.redis.host}")
    31 
    32     private String host;
    33 
    34 
    35 
    36     @Value("${spring.redis.port}")
    37 
    38     private int port;
    39 
    40 
    41 
    42     @Value("${spring.redis.timeout}")
    43 
    44     private int timeout;
    45 
    46 
    47     @Value("${spring.redis.pool.max-idle}")
    48 
    49     private int maxIdle;
    50 
    51 
    52 
    53     @Value("${spring.redis.pool.max-wait}")
    54 
    55     private long maxWaitMillis;
    56 
    57 
    58 
    59     @Bean
    60 
    61     public JedisPool redisPoolFactory() {
    62 
    63         Logger.getLogger(getClass()).info("JedisPool注入成功!!");
    64 
    65         Logger.getLogger(getClass()).info("redis地址:" + host + ":" + port);
    66 
    67         JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    68 
    69         jedisPoolConfig.setMaxIdle(maxIdle);
    70 
    71         jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
    72 
    73 
    74         JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);
    75 
    76 
    77         return jedisPool;
    78 
    79     }
    80 
    81 
    82 }
    配置文件 application.properties中加入: 
     1 #redis
     2 
     3 # Redis服务器地址
     4 
     5 spring.redis.host= localhost
     6 
     7 # Redis服务器连接端口
     8 
     9 spring.redis.port= 6379
    10 
    11 # 连接池中的最大空闲连接
    12 
    13 spring.redis.pool.max-idle= 8
    14 
    15 # 连接池中的最小空闲连接
    16 
    17 spring.redis.pool.min-idle= 0
    18 
    19 # 连接池最大连接数(使用负值表示没有限制)
    20 
    21 spring.redis.pool.max-active= 8
    22 
    23 # 连接池最大阻塞等待时间(使用负值表示没有限制)
    24 
    25 spring.redis.pool.max-wait= -1
    26 
    27 # 连接超时时间(毫秒)
    28 spring.redis.timeout= 0
    当然运行的时候要先启动redis。将自己的redis配置在以上配置中。这样session就存在redis中了。 
    上面ShiroConfig中的securityManager()方法中,我把
    1 //securityManager.setCacheManager(cacheManager());
    这行代码注了,是这样的,因为每次在需要验证的地方,比如在subject.hasRole(“admin”) 或 subject.isPermitted(“admin”)、@RequiresRoles(“admin”) 、 shiro:hasPermission=”/users/add”的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()。
    但是以为这些信息不是经常变的,所以有必要进行缓存。把这行代码的注释打开,的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()的返回结果会被redis缓存。但是这里稍微有个小问题,就是在刚修改用户的权限时,无法立即失效
    。本来我是使用了ShiroService中的clearUserAuthByUserId()想清除当前session存在的用户的权限缓存,但是没有效果。
    不知道什么原因。希望哪个大神看到后帮忙弄个解决方法。所以我干脆就把doGetAuthorizationInfo()的返回结果通过spring cache的方式加入缓存。
    1 @Cacheable(cacheNames="resources",key="#map['userid'].toString()+#map['type']")
    2     public List<Resources> loadUserResources(Map<String, Object> map) {
    3 
    4         return resourcesMapper.loadUserResources(map);
    5 
    6     }
    这样也可以实现,然后在修改权限时加上注解
    1  @CacheEvict(cacheNames="resources", allEntries=true)

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

    按钮控制

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

    1 <button shiro:hasPermission="/users/add" type="button"  onclick="$('#addUser').modal();" class="btn btn-info" >新增</button>
    这里的参数就是我们在ShiroConfig-shirFilter()权限加载时的过滤器 中的value,也就是资源的url。
    1  filterChainDefinitionMap.put(resources.getResurl(),permission);

    8.效果图

    9.运行、下载

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

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

    转自:https://www.cnblogs.com/jpfss/p/8311317.html#

  • 相关阅读:
    洛谷P1169 [ZJOI2007]棋盘制作
    洛谷P4147 玉蟾宫
    洛谷P3068 [USACO13JAN]Party Invitations S
    洛谷P3594 [POI2015]WIL-Wilcze doły
    洛谷P2564 [SCOI2009]生日礼物
    洛谷P4296 [AHOI2007]密码箱
    洛谷P2421 [NOI2002]荒岛野人
    洛谷P3990 [SHOI2013]超级跳马
    MySQL 默认引擎 默认编码
    Python 换源
  • 原文地址:https://www.cnblogs.com/chenlove/p/9366820.html
Copyright © 2011-2022 走看看