SpringBoot整合Shiro-关于登出时,Redis中缓存没有清理干净的问题(十七)

原文:https://blog.csdn.net/qq_34021712/article/details/84722724

如果是跟着我的shiro系列博客敲下来的,其实还有一个bug,这是一个网友遇到的,他在登出的时候,发现redis中当前用户身份认证缓存没有清理掉,之前在 springboot整合shiro-ehcache缓存(五) 中测试添加权限之后,清理的是所有用户的缓存,所以没有发现这个问题。

还记得上一篇博客: springboot整合shiro-实现自己登出(十六), 我们在登出方法中,清理了当前用户的 身份认证权限认证的 缓存信息,最后发现有一个key 没有清理掉,如下图:
在这里插入图片描述
为什么该key没有清除掉呢?经过debug发现,在清理 身份认证 缓存的时候,调用了ShiroRealmclearCachedAuthenticationInfo 最终调用到 RedisCacheremove 方法,但是传过来key 却是 User实体,为什么会是User实体,就是因为在 ShiroRealmdoGetAuthenticationInfo 方法返回值 SimpleAuthenticationInfo 中,第一个参数 传的是 User实体,具体 debug细节 如下:
在这里插入图片描述
而在删除 用户 权限缓存时,却没有这个问题,删除缓存时,传入的key为SimplePrincipalCollection 最终调用getRedisKeyFromPrincipalIdField 根据你在 ShiroConfig中 配置 RedisCacheManager 指定的那个字段作为缓存的前缀,根据反射获取该字段的值 并返回,具体的配置信息,可以参考之前博客的源码:
在这里插入图片描述

解决方案

第一种,判断key为User实体,强转并获取用户名

在这里插入图片描述

第二种,将ShiroRealm 的 doGetAuthenticationInfo 方法返回值 SimpleAuthenticationInfo 中,第一个参数,传username,不要传User实体

第一步:修改ShiroRealm

修改shiroRealm中的 doGetAuthenticationInfo 验证用户身份的最后一句 返回值

return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),new MyByteSource(user.getUsername()),getName());
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

    //获取用户名密码 第一种方式
    //String username = (String) authenticationToken.getPrincipal();
    //String password = new String((char[]) authenticationToken.getCredentials());

    //获取用户名 密码 第二种方式
    UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
    String username = usernamePasswordToken.getUsername();
    String password = new String(usernamePasswordToken.getPassword());

    //从数据库查询用户信息
    User user = this.userMapper.findByUserName(username);

    //可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验
    if (user == null) {
        throw new UnknownAccountException("用户名或密码错误!");
    }
    //这里将 密码对比 注销掉,否则 无法锁定  要将密码对比 交给 密码比较器
    //if (!password.equals(user.getPassword())) {
    //    throw new IncorrectCredentialsException("用户名或密码错误!");
    //}
    if ("1".equals(user.getState())) {
        throw new LockedAccountException("账号已被锁定,请联系管理员!");
    }

    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),new MyByteSource(user.getUsername()),getName());
    return info;
}

并且修改shiroRealm中 的 doGetAuthorizationInfo 方法,SecurityUtils.getSubject().getPrincipal()之前 返回的是User实体,现在 就是我们上一步中 放进去的用户名。并且这里需要再单独根据 username去数据库查询 User

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

    System.out.println("查询权限方法调用了!!!");

    //这里获取到的就是 上面方法放进去的username了
    String username = (String)SecurityUtils.getSubject().getPrincipal();
    //需要单独根据 username 从数据库查询用户信息
    User user = this.userMapper.findByUserName(username);

    //获取用户角色
    Set<Role> roles =this.roleMapper.findRolesByUserId(user.getUid());
    //添加角色
    SimpleAuthorizationInfo authorizationInfo =  new SimpleAuthorizationInfo();
    for (Role role : roles) {
        authorizationInfo.addRole(role.getRole());
    }

    //获取用户权限
    Set<Permission> permissions = this.permissionMapper.findPermissionsByRoleId(roles);
    //添加权限
    for (Permission permission:permissions) {
        authorizationInfo.addStringPermission(permission.getPermission());
    }

    return authorizationInfo;
}

第二步:修改项目中 其他地方 使用(User)SecurityUtils.getSubject().getPrincipal();的 代码

因为 我们在第一部中 已经将 Principal 从 User实体 改为了 username 所以这些相应的都需要修改,这里要看你们代码中都是在那里使用的了。

第三步:修改之前 index.html页面中的34行 <shiro:principal property=“username”/>

因为 principal为 用户名,已经不是实体了,这里再指定 property 就会报如下 异常:

2018/11/30 15:13:28.861 org.thymeleaf.TemplateEngine [] ERROR [THYMELEAF][http-nio-9090-exec-5] Exception processing template "index": Error during execution of processor 'at.pollux.thymeleaf.shiro.processor.element.HasPermissionElementProcessor' (index:15)
2018/11/30 15:13:28.862 o.s.web.servlet.DispatcherServlet [] DEBUG Error rendering view [org.thymeleaf.spring4.view.ThymeleafView@7578de10] in DispatcherServlet with name 'dispatcherServlet'
org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'at.pollux.thymeleaf.shiro.processor.element.HasPermissionElementProcessor' (index:15)

在这里插入图片描述
具体如下图,只需要使用 <shiro:principal></shiro:principal> 就可以了。
在这里插入图片描述
第四步:修改RedisCache 类中的 getStringRedisKey 方法

直接返回 key.toString 下面的 getRedisKeyFromPrincipalIdField方法 也可以直接删除了,因为不再使用它了 ,原本 它存在的意义 就是为了解决 principal放的是 User实体。

在这里插入图片描述
第五步:启动测试
将redis清空, 并将浏览器缓存清除,启动项目测试,问题已解决,至于另外两个key都是我们自定义的功能,如果想要删除的话,直接删除redis的key就行了 都是使用username 拼接的key。

管理员清理其他用户的缓存

还有这样一种情况,有testadmin两名用户, admin是管理员,在给 test用户分配新的权限之后,需要清除该用户的 权限缓存信息 ,这里有一种笨方法,如下 ,添加一个 删除用户缓存的方法, 只有 userInfo:clearCache 权限才可以执行此操作 ,并将 该权限给admin用户,在admin用户 给test用户 分配新的权限之后,可以立即清除test用户的权限缓存。

/**
 * 将该权限赋给 admin用户 使用admin用户清理其他用户的 权限缓存
 * @param username
 * @return
 */
@RequiresPermissions("userInfo:clearCache")
@RequestMapping(value = "/clearCache",method = RequestMethod.GET)
@ResponseBody
public String clearCache(String username) {

    String[] keys = new String[3];
    keys[0] = "shiro:cache:authenticationCache:"+username;
    keys[1] = "shiro:cache:authorizationCache:"+username;
    keys[2] = "shiro:cache:retrylimit:"+username;

    //原子性 命令 删除多个key
    shiroRedisTemplate.delete(CollectionUtils.arrayToList(keys));

    return "删除"+username+"权限成功";

}

我们将在下一篇讨论 :SimpleAuthenticationInfo 应该 使用username 还是User实体。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值