文章目录

  • 0、文章简介
  • 1、配置session管理
  • 2、权限管理—授权
  • 2.1、搭建环境(配置信息)
  • 2.2、过滤器设置权限
  • 2.3、注解设置权限
  • 2.4、授权
  • 2.5、在HTML页面上基于Thymeleaf的支持
  • 2.6、shiro标签
  • 3、缓存的使用
  • 3.1、为什么使用缓存
  • 3.2、导入依赖
  • 3.3、在resources下创建ehcache.xml配置文件
  • 3.4、在ShiroConfig中配置缓存
  • 4、实现 remeberMe 功能
  • 4.1、配置ShiroConfig
  • 4.2、在页面上添加rememberMe这个功能
  • 4.3、在认证的token上设置rememberMe这个属性
  • 4.4、配置过滤器
  • 4.5、测试功能


0、文章简介

本篇文章使用 springboot整合shiro,完成了:session管理、授权管理(过滤器授权、注解授权)、授权缓存管理、remeberMe等功能。

1、配置session管理
  1. 在 ShiroConfig.java 中添加如下:
/**
     * Session 管理
     */
    public DefaultWebSessionManager sessionManager() {
        logger.info("Session 管理器初始化ing...");
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 设置 session 的过期时间(单位是毫秒)(默认值是30分钟)
        sessionManager.setGlobalSessionTimeout(60000);
        // session 到期的时候是否自动删除
        sessionManager.setDeleteInvalidSessions(true);
        logger.info("Session 管理器初始化完成...");
        return sessionManager;
    }
  1. 修改安全管理器配置方法,加上配置session管理器
@Bean("securityManager")
    public SecurityManager securityManager(UserRealm userRealm) {
        logger.info("securityManager安全管理器初始化ing...");
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置自定义realm
        defaultWebSecurityManager.setRealm(userRealm);
        // 设置session管理器
        defaultWebSecurityManager.setSessionManager(sessionManager());
        logger.info("securityManager安全管理器初始化完成...");
        return defaultWebSecurityManager;
    }

这里设置的Session过期时间为1分钟,即1分钟之后,再次访问 主页,需要重新登录

2、权限管理—授权
2.1、搭建环境(配置信息)

在SpringBoot整合shiro之基本使用的基础上

给html、Controller、Service、Mapper添加上对用户的 CRUD操作,这里主要是测试权限管理,只演示效果

  1. 给控制器添加如下方法
@RequestMapping("addUser")
    public String addUser() {
        // 这里目的是测试shiro的权限管理,就不设置用户注册了
        User user = new User("username", "pwd", "salt");
        userService.addUser(user);
        return "success";
    }

    @RequestMapping("deleteUser")
    public String deleteUser(Integer id) {
        userService.deleteUser(id);
        return "success";
    }
    
    @RequestMapping("updataUser")
    public String updateUser() {
        userService.updateUser();
        return "success";
    }

	@RequestMapping("toUnAuthorized")
    public String toUnAuthorized() {
        return "unAuthorized";
    }
  1. 编写页面

2.1、向 index.html 中添加以下内容

<p><a href="/addUser">添加用户(过滤器方式授权)</a></p>
<p><a href="/updateUser">修改用户(无权限即可访问)</a></p>
<p><a href="/deleteUser">删除用户(注解方式授权)</a></p>

2.2、创建 success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>操作成功</h1>
    <h3><a href="/toIndex">返回主页</a></h3>
</body>
</html>

2.3、创建 unAuthorized.html 页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>提示页面</title>
</head>
<body>
    <h1>权限不足</h1>
    <h3><a href="/toIndex">返回主页</a></h3>
</body>
</html>
  1. 配置 ShiroConfig.java

3.1、在 ShiroConfig.java 中添加如下两个方法

/**
     *  下边两个方法表示 配置AOP对注解的支持(也就是shiro中注解的支持)
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

3.2、配置权限不足页面

在配置 shiro过滤器 的方法(shiroFilterFactoryBean)中添加这一行代码,加到map之前

// 如果没有权限访问 那么就跳转到该页面,提示权限不足
        // 该配置只能对 通过过滤器方式设置权限的生效,使用注解方式的需要使用全局异常捕获
        shiroFilterFactoryBean.setUnauthorizedUrl("/toUnAuthorized");
  1. 配置全局异常解析器,捕获 UnauthorizedException,跳转到权限不足页面

不在本文讨论范围内,如需要请自行百度,或参照 【SpringBoot整合Hibernate-invalidate(JSR303校验)】这里关于配置全局异常解析器部分

2.2、过滤器设置权限

在 shiro过滤器方法 的 map中,添加以下内容即可

注:不能加到 "/" 之后**

filterChainDefinitionMap.put("/addUser", "perms[user:add]");    // 必须拥有这个权限,才能访问 addUser
2.3、注解设置权限

修改 Controller,添加注解即可

@RequiresPermissions({"user:delete"})	// 表示必须拥有这个权限才能访问,否则会抛出异常
    @RequestMapping("deleteUser")
    public String deleteUser(Integer id) {
        userService.deleteUser(id);
        return "success";
    }
/* 常用注解:
 *	@RequiresRoles()    必须拥有某角色才能执行
 *  @RequiresUser()     必须是某用户才能执行
 *  @RequiresPermissions()	// 表示必须拥有这个权限才能访问
 */

到这里,可自行测试一下代码:

直接运行项目,点击那三个按钮,看是否达到指定的效果即可

添加:跳转到权限不足页面;修改:正常访问;删除:抛出异常

2.4、授权

通过设置 UserRealm.java 完成授权操作

@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 1. 获取当前用户的用户名
        User user = (User) principalCollection.getPrimaryPrincipal();
        // 2. 通过用户名去数据库查询用户对应的权限信息
        // 这里不操作数据库,直接赋值模拟操作
        // 3. 将查询到的权限信息封装到一个Set集合中,或是查询到的角色信息
        HashSet<String> permissions = new HashSet<>();
        permissions.add("user:add");
        permissions.add("user:delete");

        HashSet<String> roles = new HashSet<>();
        roles.add("admin");
        // 4. 创建 授权信息对象,把权限集合传到对象中,或是角色集合
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);
        // 5. 返回该对象
        return simpleAuthorizationInfo;
    }

此时再次进行测试,点击所有的按钮,都可以正常访问了

2.5、在HTML页面上基于Thymeleaf的支持
  1. 导入依赖
<!-- 导入thymeleaf对shiro的支持包 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
  1. 配置 支持shiro标签

在 ShiroConfig.java 中添加如下方法

/**
     * 配置方言: shiro-dialect,是为了能在html页面引用shiro标签
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
// 在页面html标签中加入以下内容

xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
2.6、shiro标签
<!-- 自行测试即可
   <shiro:authenticated>
       <span>
           用户的身份验证是成功的
       </span>
   </shiro:authenticated>

   <shiro:guest>
       <span>你是游客</span>
   </shiro:guest>

   <shiro:hasPermission name="user:add">
        <span>用户必须具有某一个权限才能访问</span>
   </shiro:hasPermission>

   <shiro:hasAllRoles name="buyer,seller">
       <span>拥有某一个角色下面才显示</span>
   </shiro:hasAllRoles>

   <shiro:lacksPermission>
       <span>没有某一个权限的时候才能访问</span>
   </shiro:lacksPermission>

   <shiro:lacksRole name="xxx">
        <span>没有某一个角色的时候才能访问</span>
   </shiro:lacksRole>

   <shiro:notAuthenticated>
       <span>没有认证通过才能显示</span>
   </shiro:notAuthenticated>
-->
3、缓存的使用
3.1、为什么使用缓存

为什么要使用缓存?

测试 授权的时候,每一次都会去访问 realm中的授权的方法

这样的话每次都要从数据库中查询权限,数据库的压力比较大,所以缓存就应运而生了

缓存:缓存的是授权信息

3.2、导入依赖
<!-- 缓存需要的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version>
        </dependency>
3.3、在resources下创建ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
    <diskStore path="D:\mytemp"/>

    <cache name="users"
           timeToLiveSeconds="300"
           maxEntriesLocalHeap="1000"/>
    <!--
        name:缓存名称。
        maxElementsInMemory:缓存最大个数。
        eternal:对象是否永久有效,一但设置了,timeout将不起作用。
        timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
        timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
        overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
        maxElementsOnDisk:硬盘最大缓存个数。
        diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
        clearOnFlush:内存数量最大时是否清除。
    -->
    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU"/>
</ehcache>
3.4、在ShiroConfig中配置缓存

1、注入缓存配置

/**
     * 配置 缓存管理器
     */
    @Bean
    public EhCacheManager ehCacheManager() {
        logger.info("ehCacheManager缓存 初始化ing...");
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");        
        logger.info("ehCacheManager缓存 初始化完成...");
        return ehCacheManager;
    }

2、在安全管理器中配置缓存管理器

@Bean("securityManager")
    public SecurityManager securityManager(UserRealm userRealm) {
        logger.info("securityManager安全管理器初始化ing...");
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置自定义realm
        defaultWebSecurityManager.setRealm(userRealm);
        // 设置session管理器
        defaultWebSecurityManager.setSessionManager(sessionManager());
        // 设置缓存管理器
        defaultWebSecurityManager.setCacheManager(ehCacheManager());
        logger.info("securityManager安全管理器初始化完成...");
        return defaultWebSecurityManager;
    }

至此,缓存已经配置完成,再次测试,点击按钮的时候,无论点击多少次,都只执行了一次授权方法

4、实现 remeberMe 功能

shiro使用了 Session + Cookie的模式来做 记住我 这个功能

4.1、配置ShiroConfig
/**
     *  配置 CookieRememberMe管理器,实现登录时记住我的功能
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        logger.info("CookieRememberMeManager 初始化ing...");
        CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
        //设置自定义的Cookie
        rememberMeManager.setCookie(simpleCookie());
        logger.info("CookieRememberMeManager 初始化完成...");
        return rememberMeManager;
    }
    /**
     * 自定义一个Cookie对象,保存rememberMe的cookie信息
     */
    @Bean
    public SimpleCookie simpleCookie(){
        logger.info("SimpleCookie 自定义Cookie对象初始化ing...");
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        // 最大保存时间,单位秒,这里表示保存30天
        simpleCookie.setMaxAge(2592000);
        logger.info("SimpleCookie 自定义Cookie对象初始化完成...");
        return simpleCookie;
    }
    
    // 在安全管理器中添加 CookieRememberMeManager 
    /**
     * 配置 SecurityManager(安全管理器)
     */
    @Bean("securityManager")
    public SecurityManager securityManager(UserRealm userRealm) {
        logger.info("securityManager安全管理器初始化ing...");
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置自定义realm
        defaultWebSecurityManager.setRealm(userRealm);
        // 设置session管理器
        defaultWebSecurityManager.setSessionManager(sessionManager());
        // 设置缓存管理器
        defaultWebSecurityManager.setCacheManager(ehCacheManager());
        // 设置rememberMe管理器
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        logger.info("securityManager安全管理器初始化完成...");
        return defaultWebSecurityManager;
    }
4.2、在页面上添加rememberMe这个功能
<form action="/login" method="post">
    用户名:<input type="text" name="name" ><span th:text="${session.userNameError}"></span><br>
    密码:<input type="text" name="password" ><span th:text="${session.passwordError}"></span><br>
    记住我:<input type="checkbox" name="rememberMe"><br>
    <input type="submit" value="登录">
</form>
4.3、在认证的token上设置rememberMe这个属性

UserController.java 中 login方法

@PostMapping("login")
    public String login(User user, HttpSession session, boolean rememberMe) {
        logger.info("接受到前端的数据 rememberMe = " + rememberMe);
        // 封装成请求对象
        UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
        // 设置记住我这个功能是否开启
        token.setRememberMe(rememberMe);
        // 获取登录的主体对象
        Subject subject = SecurityUtils.getSubject();
        // ...
    }
4.4、配置过滤器

在过滤器中配置哪些页面在使用了rememberme这个功能之后可以直接访问

直接在 map中添加即可

注意:注意过滤器的顺序,在 /** 之前

// 表示如果使用了rememberMe这个功能,就可以不需要认证直接访问 /updateUser
filterChainDefinitionMap.put("/updateUser", "user");
4.5、测试功能
  1. 配置完成之后直接启动项目
  2. 进行登录,登录时勾选 记住我
  3. 进入主页,测试是否能正常访问权限
  4. 然后关闭浏览器,目的是消除 session
  5. 再次打开浏览器,直接访问主页,会直接进入到登录页面
  6. 不要登录,从地址栏直接访问 /updateUser ,能正常访问证明 rememberMe 功能完成

注:

如果第一次登录选择记住我,第二次登录的时候没有选择记住我,那么再次关闭浏览器,重新访问 /updateUser 则无法访问。

原因:第二次登录时,会接收到前端传递的数据 rememberMe = false,重新设置到了token中,cookie会被清除