当前位置 博文首页 > 快乐的小三菊的博客:springboot + shiro 实现自己的登出

    快乐的小三菊的博客:springboot + shiro 实现自己的登出

    作者:[db:作者] 时间:2021-07-13 16:07

    背景:

    ? ? ? ?如果想要实现登出功能,常见的实现方式就是在?ShiroConfig 类的?shiroFilterFactoryBean() 方法中配置一个标签,这种方式是 shiro 自带的登出方式,只要拦截到访问 /logout 的请求,就会被 logout 对应的 LogoutFilter 拦截,自动登出。配置如下所示:

        @Bean
    	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
    	    // ....	
    		Map<String, String> map = new LinkedHashMap<>();
    		// logout 使用的是 shiro 提供的过滤器
    		map.put("/logout", "logout");
    		shiroFilter.setFilterChainDefinitionMap(map);
            // ....
    		return shiroFilter;
    	}

    实现自定义登出方式原因:

    ? ? ? ?为什么要实现自己的自定义登出?虽然 shiro 的默认登出会清理用户的 session 信息,并且也会清理掉 redis 中缓存的?用户身份认证?和?授权认证?的相关信息。但是有时候,我们还有自己的一些业务需要处理,比如说前面做的 ”限制用户的登录次数“”并发登录的人数“,使用 shiro 的默认登出时这些是不会被删除的,假如我们想要删除这些 key 呢? 或者说在用户登出时,要记录一些日志信息存储当前用户在线时长等等,这个时候我们就需要是实现自定义登出。

    自定义登出方式的具体方法:

    ? ? ? ?方式一:不配置默认的 logout ,在自己的 controller 中写一个方法对外提供 http 接口,实现自己的登出逻辑,参考代码如下所示:

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

    ? ? ? ?方式二:写一个类继承?LogoutFilter 过滤器,并重写 preHandle() 方法。代码如下所示:

    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.authc.LogoutFilter;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    
    import com.shiro.CustomRealm;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    /**
     * @description: 自定义 LogoutFilter
     */
    public class ShiroLogoutFilter extends LogoutFilter {
    
        /**
         * 自定义登出,登出之后,清理当前用户redis部分缓存信息
         * @param request
         * @param response
         * @return
         * @throws Exception
         */
        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    
            // 登出操作 清除缓存  subject.logout() 可以自动清理缓存信息, 这些代码是可以省略的  这里只是做个笔记 表示这种方式也可以清除
            Subject subject = getSubject(request,response);
            DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
            CustomRealm shiroRealm = (CustomRealm) securityManager.getRealms().iterator().next();
            PrincipalCollection principals = subject.getPrincipals();
            shiroRealm.clearCache(principals);
            // 登出
            subject.logout();
            // 获取登出后重定向到的地址
            String redirectUrl = getRedirectUrl(request,response,subject);
            // 重定向
            issueRedirect(request,response,redirectUrl);
            return false;
        }
    }

    ? ? ? ?然后在 ShiroConfig 类中配置 shiroLogoutFilter ,需要注意的是在配置?shiroLogoutFilter 时,这个类不需要加 @Bean 注解。

    public ShiroLogoutFilter shiroLogoutFilter(){
    	    ShiroLogoutFilter shiroLogoutFilter = new ShiroLogoutFilter();
    	    //配置登出后重定向的地址,等出后配置跳转到登录接口
    	    shiroLogoutFilter.setRedirectUrl("/login");
    	    return shiroLogoutFilter;
    	}

    ? ? ? ?最后一步,将配置的 shiroLogout 注入到?ShiroFilterFactoryBean 中,代码如下所示:

        @Bean
    	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {		
            // ....
    		LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
            // logout 将执行自定义的filter 不再走默认的LogoutFilter
            filtersMap.put("logout", shiroLogoutFilter());
    		shiroFilter.setFilters(filtersMap);
            // ....
    		shiroFilter.setFilterChainDefinitionMap(map);
    		return shiroFilter;
    	}

    可能出现的问题:

    ? ? ? ?无论是使用 subject.logout() 还是使用 shiroRealm?中的自定义方法 shiroRealm.clearCache(principals) ,都只能清除权限的缓存信息,?却无法清除 redis 中身份认证的缓存信息,?如下,具体如何解决这个问题,请看我的下一篇文章。

    ?

    cs