Springboot整合Shiro


pom依赖

<dependencies>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.8.0</version>
        </dependency>
        <!--整合mybatis-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--整合druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--thymeleaf与shiro整合-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.1.0</version>
        </dependency>
        <!--导入hutool的依赖-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.7</version>
        </dependency>
        <!--shiro缓存,添加ehcache-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>

前端页面(thymeleaf整合shiro)

首页index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<h2 th:text="${msg}"></h2>
<div shiro:guest="">
    <a th:href="@{/toLogin}">登录</a>
</div>
<div shiro:hasPermission="user:add">
    <a th:href="@{/toAdd}">add</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/toUpdate}">update</a>
</div>
<div shiro:hasRole="vip1">
    <p>vip1</p>
</div>
<div shiro:hasRole="vip2">
    <p>vip2</p>
</div>

<div shiro:user>
    <a th:href="@{/buy}">记住我能看到,但认证才能用(比如支付功能)</a>
</div>

<div shiro:user="true">
    记住我或认证都能看到哦
</div>

<div shiro:user>
    <a th:href="@{/logout}">注销</a>
</div>
</body>
</html>

登录页login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登录</h1>
<p th:text="${msg}" style="color: red"></p>
<form action="/login" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="checkbox" name="rememberMe" value="1">记住我 <br>
    <input type="submit">
</form>

</body>
</html>

再写几个要跳转的页面,如add,update,noauth(不具有权限跳转到的页面),里面随便放点东西即可
在这里插入图片描述

thymeleaf中shiro标签解释

<!-- 当前用户是否为“游客”,如果是未认证,也未记住的用户,那么就显示该 p 标签中的内容 -->
<p shiro:guest="">Please <a href="login.html">login</a></p>
<!-- 未认证的用户但已记住,与下面的 authenticated 标签相对应。与 guest 标签的区别是,该标签包含已记住用户。 -->
<p shiro:notAuthenticated="">
    Please <a href="login.html">login</a> in order to update your credit card information.
</p>

<!-- 认证通过或已记住的用户,则显示该 p 标签中的内容 -->
<p shiro:user="">
    Welcome back John! Not John? Click <a href="login.html">here</a> to login.
</p>
<!-- 已认证通过,但未记住的用户。这是与 user 标签的区别所在。 -->
<p shiro:authenticated="">
    Hello, <span shiro:principal=""></span>, how are you today?
</p>
<a shiro:authenticated="" href="updateAccount.html">Update your contact information</a>
<!-- 输出当前用户信息,通常为登录帐号信息 -->
<p>Hello, <shiro:principal />, how are you today?</p>
<!-- 验证当前用户是否具有该 admin 角色,若拥有,则显示 a 标签的内容 -->
<a shiro:hasRole="admin" href="admin.html">Administer the system</a>
<!-- 与 hasRole 标签逻辑相反,当用户不属于该 developer 角色时显示 -->
<p shiro:lacksRole="developer">
    Sorry, you are not allowed to developer the system.
</p>
<!-- 验证当前用户是否同时具有以下所有角色 -->
<p shiro:hasAllRoles="developer, product">
    You are a developer and a admin.
</p>
<!-- 验证当前用户是否具于以下任意一个角色 -->
<p shiro:hasAnyRoles="admin, vip, developer">
    You are a admin, vip, or developer.
</p>
<!-- 验证当前用户是否拥有 update 权限 -->
<a shiro:hasPermission="update" href="createUser.html">添加用户</a>
<!-- 与 hasPermission 标签逻辑相反,当前用户不拥有指定权限 delete -->
<p shiro:lacksPermission="delete">
    Sorry, you are not allowed to delete user accounts.
</p>
<!-- 验证当前用户是否同时拥有以下所有权限 -->
<p shiro:hasAllPermissions="add, update">
    You can see or add users.
</p>
<!-- 验证当前用户是否拥有以下任意一个权限 -->
<p shiro:hasAnyPermissions="add, update, delete">
    You can see or delete users.
</p>

数据库(整合mybatis)

在这里插入图片描述

application.yml

spring:
  datasource:
    username : root
    password: 123456
    url : jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis:
  type-aliases-package: com.govd.pojo
  mapper-locations: classpath:mapper/*.xml

log4j配置文件log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

编写pojo,这里记得一定要序列化
在这里插入图片描述

druid配置

@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource DruidDataSource(){
        return new DruidDataSource();
    }
}

UserMapper

@Mapper
@Repository
public interface UserMapper {
    public User queryUserByName(String name);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.govd.mapper.UserMapper">
    <select id="queryUserByName" resultType="user">
        select * from user where name=#{name}
    </select>
</mapper>

之后编写对应的service即可


理解shiro的几个组成部分

在这里插入图片描述

●subject: 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject, Subject代表了当前的用户,这个用户不-定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager; Subject其实是一一个门面, SecurityManageer 才是
实际的执行者
●SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互, 并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色
●Realm: Shiro从Realm获取安全数据 (如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看DataSource;


编写Shiro配置类(shiroConfig.class)

思路:ShiroFilterFactoryBean会拦截前端请求交给DefaultWebSecurityManager,再交给MyRealm进行认证和授权处理
主要编写MyRealm、DefaultWebSecurityManager、ShiroFilterFactoryBean三个bean对象,
三个对象从前往后写。

ShiroFilterFactoryBean
内置过滤器参数说明:

anon: 无需认证即可访问
authc: 必须认证才能用
user: 必须拥有 “记住我” 功能才能用
perms: 拥有对某个资源的权限才能用
role: 拥有某个角色权限才能访问

设置登录页面

setLoginUrl("/toLogin");

注销登录用户的两种方式
第一种,在controller中处理

@RequestMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "index";
    }

第二种,ShiroFilterFactoryBean中添加注销过滤器
在这里插入图片描述

设置没有权限时跳转到的页面

setUnauthorizedUrl("/noauth")

在这里插入图片描述

完整ShiroFilterFactoryBean配置

	@Bean
    public ShiroFilterFactoryBean bean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        //添加shiro的内置过滤器
        /*
        anon: 无需认证即可访问
        authc: 必须认证才能用
        user: 必须拥有 “记住我” 功能才能用
        perms: 拥有对某个资源的权限才能用
        role: 拥有某个角色权限才能访问
        */

        Map<String,String> filterMap=new HashMap<>();

        //登陆后授权,正常情况下没有授权会跳转到未授权页面
        filterMap.put("/toAdd","perms[user:add]");
        filterMap.put("/toUpdate","perms[user:update]");
        //设置注销过滤器
        filterMap.put("/logout","logout");

        /**
         *    /** 匹配所有的路径
         *   通过Map集合组成了一个拦截器链 ,自顶向下过滤,一旦匹配,则不再执行下面的过滤
         *   如果下面的定义与上面冲突,那按照了谁先定义谁说了算
         *   所以/** 一定要配置在最后
         *   这里是否要对所有路径进行认证视情况而定,因为一些路由跳转可能在没登陆出现导致出错,所以这里考虑清楚
         **/
        //filterMap.put("/**", "authc");
        
		// 将拦截器链设置到shiro中
        bean.setFilterChainDefinitionMap(filterMap);
        //设置登录页面
        bean.setLoginUrl("/toLogin");
		// 登录成功后要跳转的链接
        //shiroFilterFactoryBean.setSuccessUrl("/index");

        //设置未授权页面
        bean.setUnauthorizedUrl("/noauth");

        return bean;
    }

编写DefaultWebSecurityManager

	@Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        return securityManager;
    }

编写MyRealm类

public class MyRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    //执行授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //认证之后,如果前端shiro标签中有出现需要权限的标签,或者过滤器中某个链接需要权限,就会进行认证
        System.out.println("执行了授权");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得当前subject
        Subject subject = SecurityUtils.getSubject();
        //获得当前的principal,也就是认证完后我们放入的信息
        User currentUser = (User) subject.getPrincipal();
        //添加权限
        info.addStringPermission(currentUser.getPerms());
        //添加角色
        info.addRole(currentUser.getRole());

        return info;
    }

    //执行认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {
        System.out.println("执行了认证");
        UsernamePasswordToken token = (UsernamePasswordToken) Token;
        //从数据库中查询该用户
        User user = userService.queryUserByName(token.getUsername());
        //如果不存在该用户,返回一个空错误,前端也可以相应显示提示
        if(user==null){
            return null;
        }
        //第一个参数为principal;第二个参数为从数据库中查出的用于验证的密码,shiro中密码验证不需要我们自己去做;第三个参数为realmName
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

将其在shiroConfig中注册成bean

	@Bean
    public MyRealm myRealm(){
        MyRealm myShiroRealm = new MyRealm();
        return myShiroRealm;
    }

controller

编写routerController

@Controller
public class routerController {
    //跳到首页
    @RequestMapping({"/", "/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello shiro");
        return "index";
    }
    //跳到登录页
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/toAdd")
    public String toAdd(){
        return "add";
    }

    @RequestMapping("/toUpdate")
    public String toUpdate(){
        return "update";
    }

    //跳到未授权页面
    @RequestMapping("/noauth")
    public String tonoauth(){
        return "noauth";
    }

    //用于测试记住我和认证的区别
    @RequestMapping("/buy")
    public String buy(){
        Subject subject = SecurityUtils.getSubject();
        //只有认证后才能访问,如果只是记住我则需要先登录
        if(!subject.isAuthenticated()){
            return "redirect:/toLogin";
        }
        return "add";
    }

    //登录认证
    @RequestMapping("/login")
    public String login(String username,String password,Integer rememberMe,Model model){
        Subject subject = SecurityUtils.getSubject();
        //令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        if(rememberMe!=null&&rememberMe==1){
            token.setRememberMe(true);
        }

        try {
            //登录认证
            subject.login(token);
            return "index";
        }catch (UnknownAccountException e){ //返回null就会进入这里
            model.addAttribute("msg","用户名不存在!");
            return "login";
        }catch (IncorrectCredentialsException e){ //密码错误就会进入这里
            model.addAttribute("msg","密码错误!");
            return "login";
        }

    }

    //注销
    @RequestMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "index";
    }
    
//    第二种注销方式,通过过滤器链注销,这里就直接返回首页即可
//    @RequestMapping("/logout")
//    public String logout(){
//        return "index";
//    }

}

记住我

记住我功能是要在用户登录成功以后,假如关闭浏览器,下次再访问系统资源(例如首页doIndexUI)时,无需再执行登录操作。
在这里插入图片描述
前端:
在这里插入图片描述
在Controller中的login方法中基于是否选中记住我,设置token的setRememberMe方法。
在这里插入图片描述
在ShiroConfig配置类中添加记住我配置,关键代码如下:

@Bean(name = "rememberMeManager")
    public CookieRememberMeManager rememberMeManager(){
        //cookie管理器
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        //cookie的名字
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //设置有效期时间30天
        simpleCookie.setMaxAge(259200);
        cookieRememberMeManager.setCookie(simpleCookie);
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));

        return cookieRememberMeManager;
    }

这里要设置cookie加密的密钥是为了给他指定一种加密方式,否则会出现关闭浏览器后再打开记住我失效,得刷新网页后才正常,因为两次加密后不一样,就像拿密钥b去匹配上次的密钥a,会导致失效

这里还有一个问题,困扰了我好久,那就是用了记住我之后关闭浏览器再次打开,第一次访问的时候总会出现错误页面,刷新之后才正常,而注意到url上总有一个jsession=******** 的东西,查了好久终于解决

解决Shiro第一次重定向url携带jsessionid问题
Shiro在进行第一次重定向时会在url后携带jsessionid,导致访问400。
解决办法:

创建一个DefaultWebSessionManager类实例,并将它的sessionIdUrlRewritingEnabled属性设置成false
再在DefaultWebSecurityManager类中将上面的实例设置为它的SessionManager

//创建DefaultWebSessionManager类
@Bean
public DefaultWebSessionManager mySessionManager(){
    DefaultWebSessionManager defaultSessionManager = new DefaultWebSessionManager();
    //将sessionIdUrlRewritingEnabled属性设置成false
    defaultSessionManager.setSessionIdUrlRewritingEnabled(false);
    return defaultSessionManager;
}

修改修改securityManager的配置,为securityManager注入rememberManager对象和defaultSessionManager对象

@Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm, @Qualifier("rememberMeManager") CookieRememberMeManager rememberMeManager, @Qualifier("mySessionManager") DefaultWebSessionManager webSessionManager, ){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(webSessionManager);
      
        return securityManager;
    }

开启缓存

缓存是提供性能的重要手段。缓存适合那些经常不变动的数据,比如系统中用户的信息和权限不会经常改变,特别适合缓存起来供下次使用。这样减少了系统查询数据库的次数,提升了性能。shiro自身不实现缓存,而是提供缓存接口,让其他第三方实现,经常使用ehcache缓存。

有两种缓存方式,但较多使用ehcache

在resources下面新建config文件夹,并创建ehcache-shiro.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">

    <!--
        缓存对象存放路径
        java.io.tmpdir:默认的临时文件存放路径。
        user.home:用户的主目录。
        user.dir:用户的当前工作目录,即当前程序所对应的工作路径。
        其它通过命令行指定的系统属性,如“java –DdiskStore.path=D:\\abc ……”。
    -->
    <diskStore path="java.io.tmpdir"/>

    <!--

       name:缓存名称。
       maxElementsOnDisk:硬盘最大缓存个数。0表示不限制
       maxEntriesLocalHeap:指定允许在内存中存放元素的最大数量,0表示不限制。
       maxBytesLocalDisk:指定当前缓存能够使用的硬盘的最大字节数,其值可以是数字加单位,单位可以是K、M或者G,不区分大小写,
                          如:30G。当在CacheManager级别指定了该属性后,Cache级别也可以用百分比来表示,
                          如:60%,表示最多使用CacheManager级别指定硬盘容量的60%。该属性也可以在运行期指定。当指定了该属性后会隐式的使当前Cache的overflowToDisk为true。
       maxEntriesInCache:指定缓存中允许存放元素的最大数量。这个属性也可以在运行期动态修改。但是这个属性只对Terracotta分布式缓存有用。
       maxBytesLocalHeap:指定当前缓存能够使用的堆内存的最大字节数,其值的设置规则跟maxBytesLocalDisk是一样的。
       maxBytesLocalOffHeap:指定当前Cache允许使用的非堆内存的最大字节数。当指定了该属性后,会使当前Cache的overflowToOffHeap的值变为true,
                             如果我们需要关闭overflowToOffHeap,那么我们需要显示的指定overflowToOffHeap的值为false。
       overflowToDisk:boolean类型,默认为false。当内存里面的缓存已经达到预设的上限时是否允许将按驱除策略驱除的元素保存在硬盘上,默认是LRU(最近最少使用)。
                      当指定为false的时候表示缓存信息不会保存到磁盘上,只会保存在内存中。
                      该属性现在已经废弃,推荐使用cache元素的子元素persistence来代替,如:<persistence strategy=”localTempSwap”/>。
       diskSpoolBufferSizeMB:当往磁盘上写入缓存信息时缓冲区的大小,单位是MB,默认是30。
       overflowToOffHeap:boolean类型,默认为false。表示是否允许Cache使用非堆内存进行存储,非堆内存是不受Java GC影响的。该属性只对企业版Ehcache有用。
       copyOnRead:当指定该属性为true时,我们在从Cache中读数据时取到的是Cache中对应元素的一个copy副本,而不是对应的一个引用。默认为false。
       copyOnWrite:当指定该属性为true时,我们在往Cache中写入数据时用的是原对象的一个copy副本,而不是对应的一个引用。默认为false。
       timeToIdleSeconds:单位是秒,表示一个元素所允许闲置的最大时间,也就是说一个元素在不被请求的情况下允许在缓存中待的最大时间。默认是0,表示不限制。
       timeToLiveSeconds:单位是秒,表示无论一个元素闲置与否,其允许在Cache中存在的最大时间。默认是0,表示不限制。
       eternal:boolean类型,表示是否永恒,默认为false。如果设为true,将忽略timeToIdleSeconds和timeToLiveSeconds,Cache内的元素永远都不会过期,也就不会因为元素的过期而被清除了。
       diskExpiryThreadIntervalSeconds :单位是秒,表示多久检查元素是否过期的线程多久运行一次,默认是120秒。
       clearOnFlush:boolean类型。表示在调用Cache的flush方法时是否要清空MemoryStore。默认为true。
       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       maxElementsInMemory:缓存最大数目
       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
            memoryStoreEvictionPolicy:
               Ehcache的三种清空策略;
               FIFO,first in first out,这个是大家最熟的,先进先出。
               LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
               LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="0"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />

    <!-- 授权缓存 -->
    <cache name="authorizationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <!-- 认证缓存 -->
    <cache name="authenticationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

</ehcache>

在shiroConfig中添加bean

	//  缓存配置
    //shiro自带的MemoryConstrainedCacheManager作缓存
    // 但是只能用于本机,在集群时就无法使用,需要使用ehcache
    @Bean(name = "cacheManager")
    public CacheManager cacheManager() {
        MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//使用内存缓存
        return cacheManager;
    }

    //配置ehcache,推荐使用
    @Bean(name = "ehCacheManager")
    public EhCacheManager ehCacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
        return ehCacheManager;
    }

修改myrealm

	@Bean
    public MyRealm myRealm(){
        MyRealm myShiroRealm = new MyRealm();
        myShiroRealm.setCachingEnabled(true);
        //启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
        myShiroRealm.setAuthenticationCachingEnabled(true);
        //缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
        myShiroRealm.setAuthenticationCacheName("authenticationCache");
        //启用授权缓存,即缓存AuthorizationInfo信息,默认false
        myShiroRealm.setAuthorizationCachingEnabled(true);
        //缓存AuthorizationInfo信息的缓存名称  在ehcache-shiro.xml中有对应缓存的配置
        myShiroRealm.setAuthorizationCacheName("authorizationCache");
        //myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return myShiroRealm;

    }

修改securityManager

 @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm, @Qualifier("rememberMeManager") CookieRememberMeManager rememberMeManager, @Qualifier("mySessionManager") DefaultWebSessionManager webSessionManager, @Qualifier("ehCacheManager")EhCacheManager ehCacheManager){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(webSessionManager);
        //设置缓存管理
        //第一种缓存
        //securityManager.setCacheManager(cacheManager);
       //ehcache缓存,推荐
        securityManager.setCacheManager(ehCacheManager);

        return securityManager;
    }

修改MyRealm(可改可不改,改了只是可以更加自定义化)

public class MyRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    //执行授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //认证之后,如果前端shiro标签中有出现需要权限的标签,或者过滤器中某个链接需要权限,就会进行认证
        System.out.println("执行了授权");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得当前subject
        Subject subject = SecurityUtils.getSubject();
        //获得当前的principal,也就是认证完后我们放入的信息
        User currentUser = (User) subject.getPrincipal();
        //添加权限
        info.addStringPermission(currentUser.getPerms());
        //添加角色
        info.addRole(currentUser.getRole());

        return info;
    }

    //执行认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {
        System.out.println("执行了认证");
        UsernamePasswordToken token = (UsernamePasswordToken) Token;
        //从数据库中查询该用户
        User user = userService.queryUserByName(token.getUsername());
        //如果不存在该用户,返回一个空错误,前端也可以相应显示提示
        if(user==null){
            return null;
        }
        //第一个参数为principal;第二个参数为从数据库中查出的用于验证的密码,shiro中密码验证不需要我们自己去做;第三个参数为realmName
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }

    /**
     * 重写方法,清除当前用户的的 授权缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    /**
     * 重写方法,清除当前用户的 认证缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    /**
     * 自定义方法:清除所有 授权缓存
     */
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    /**
     * 自定义方法:清除所有 认证缓存
     */
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    /**
     * 自定义方法:清除所有的  认证缓存  和 授权缓存
     */
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }
}

现在有了缓存,认证之后关于权限的授权就只需要走一次方法,而不需要频繁的调用,提高了性能


拓展功能

shiroConfig还可以自定义很多其他功能,思路就是创建对应功能的bean,然后相应修改securityManager和myrealm即可。

	/**
     * 密码匹配凭证管理器
     *
     * @return
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        log.info("hashedCredentialsMatcher()");
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

        hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(1024);// 散列的次数,比如散列两次,相当于md5(md5(""));

        return hashedCredentialsMatcher;
    }
    
	 /**
     * 开启shiro aop注解支持
     * 使用代理方式;所以需要开启代码支持
     * @param securityManager
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     * 开启cglib代理
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
	
	//在用spring管理我们的类的时候有时候希望有些属性值是来源于一些配置文件,系统属性,或者一些方法调用的结果,
    // 对于前两种使用方式可以使用spring的PropertyPlaceholderConfigurer类来注入,
    // 对于后一种则可以使用org.springframework.beans.factory.config.MethodInvokingFactoryBean类来生成需要注入的bean的属性。
    // 通过MethodInvokingFactory Bean类,可注入方法返回值。
    // MethodInvokingFactoryBean用来获得某个方法的返回值,该方法既可以是静态方法,也可以是实例方法。
    // 该方法的返回值可以注入bean实例属性,也可以直接定义成bean实例
    //可查看http://blog.sina.com.cn/s/blog_72ef7bea0102wa0v.html
    /**
     * 让某个实例的某个方法的返回值注入为Bean的实例
     * Spring静态注入
     * @param myShiroRealm
     * @param rememberMeManager
     * @param ehCacheManager
     * @return
     */
    @Bean(name = "methodInvokingFactoryBean")
    public MethodInvokingFactoryBean methodInvokingFactoryBean(
            @Qualifier("myShiroRealm") MyShiroRealm myShiroRealm,
            @Qualifier("rememberMeManager") CookieRememberMeManager rememberMeManager,
            @Qualifier("ehCacheManager")EhCacheManager ehCacheManager){
        MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
        factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        factoryBean.setArguments(new Object[]{securityManager(myShiroRealm, rememberMeManager,ehCacheManager)});
        return factoryBean;
    }

如果自定义了密码加密验证,则修改myRealm
注意这里如果用了加密验证,则数据库一开始存的就是加密后的密码,然后也要对前端传过来的数据进行加密处理后再进行匹配验证

 @Bean(name = "myRealm")
    public MyRealm myRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCachingEnabled(true);
        //启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
        myShiroRealm.setAuthenticationCachingEnabled(true);
        //缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
        myShiroRealm.setAuthenticationCacheName("authenticationCache");
        //启用授权缓存,即缓存AuthorizationInfo信息,默认false
        myShiroRealm.setAuthorizationCachingEnabled(true);
        //缓存AuthorizationInfo信息的缓存名称  在ehcache-shiro.xml中有对应缓存的配置
        myShiroRealm.setAuthorizationCacheName("authorizationCache");
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return new MyShiroRealm();
    }

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐