Shiro整合EhCache缓存(二)

1、授权、角色认证

用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断,这个工具就是Reaml的doGetAuthorizationInfo方法进行判断,触发权限判断的有两种方式:

  1. 在页面中通过shiro:****属性判断
  2. 在接口服务中通过注解@Requires****进行判断

1.1、后端接口服务注解

通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加在业务方法上,一般加在控制器方法上。常用注解如下:

  1. @RequiresAuthentication : 验证用户是否登录,等同于方法subject.isAuthenticated()

  2. @RequiresUser : 验证用户是否被记忆,登录认证成功subject.isAuthenticated() 为true,登录后被记忆subject.isRemembered()为true

  3. @RequiresGuest : 验证是否是一个 guest 游客的请求,此时subject.getPrincipal()为 null

  4. @RequiresRoles :验证subject是否有相应角色,有角色访问方法,没有则会抛出异常

    AuthorizationException ,例如

    // 只有subject有aRoleName角色才能访问方法someMethod()
    @RequiresRoles("aRoleName")
    void someMethod();
    
  5. @RequiresPermissions:验证 subject 是否有相应权限,有权限访问方法,没有则会抛出异常 AuthorizationException ,例如

// subject 必须同时含有 file:read 和 write:a.txt 权限才能访问方法 someMethod()
@RequiresPermissions("file:read","write:a.txt")
void someMethod();

1.2、授权-获取角色

  1. 添加controller方法
/**
 * 授权admin角色
 * @return
 */
@RequiresRoles({"admin"})
@GetMapping("userLoginRoles")
@ResponseBody
public String userLoginRoles() {
    System.out.println("登录认证验证角色");
    return "验证角色成功";
}
  1. 修改main.html
<h1>Shiro 登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
<a href="/logout">退出登录</a>
<br>
<a href="/LoginController/userLoginRoles">测试授权</a>
  1. 修改CustomerRealm方法
/**
 * 自定义授权方法:获取当前登录用户权限信息,返回给Shiro用来进行授权对比
 * @param principalCollection
 * @return
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("进入自定义授权方法");
    //1.创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //2.存储角色
    info.addRole("admin");
    //3.返回
    return info;
}
  1. 访问:localhost:8080/LoginController/login 输入账号密码,点击测试授权,显示验证角色成功,说明张三具有admin的角色

在这里插入图片描述

接下来我们结合数据库

  1. 创建库表
CREATE TABLE `role` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
 `name` VARCHAR(30) DEFAULT NULL COMMENT '角色名',
 `desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',
 `realname` VARCHAR(20) DEFAULT NULL COMMENT '角色显示名',
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色表';

INSERT INTO `role` VALUES (1, 'admin', '所有权限', '管理员');
INSERT INTO `role` VALUES (2, 'userManager', '用户管理权限', '用户管理员');

CREATE TABLE `role_user` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
 `uid` BIGINT(20) DEFAULT NULL COMMENT '用户 id',
 `rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色用户映射
表';

INSERT INTO `role_user` VALUES (1, 1, 1);
INSERT INTO `role_user` VALUES (2, 1, 2);
INSERT INTO `role_user` VALUES (3, 2, 2);

在这里插入图片描述

  1. 根据用户名查询对应角色信息
SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE 
uid=(SELECT id FROM USER WHERE NAME='张三'));
  1. mapper方法
@Repository
public interface UserMapper extends BaseMapper<User> {

    /**
     * 根据用户查询对应角色信息
     * @param principal
     * @return
     */
    @Select("select name from role where id in(select rid from role_user where uid=(select id from user where name = #{principal}))")
    List<String> getUserRoleInfoMapper(@Param("principal")String principal);

}
  1. service接口
public interface UserService {
    /**
     * 用户登录
     * @param name
     * @return
     */
    User getUserInfoByName(String name);

    /**
     * 根据用户查询角色信息
     * @param principal
     * @return
     */
    List<String> getUserRoleInfo(String principal);
}

实现类:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;


    /**
     * 用户登录
     * @param name
     * @return
     */
    @Override
    public User getUserInfoByName(String name) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name",name);
        User user = userMapper.selectOne(wrapper);
        return user;
    }

    /**
     * 根据用户查询角色信息
     * @param principal
     * @return
     */
    @Override
    public List<String> getUserRoleInfo(String principal) {
        return userMapper.getUserRoleInfoMapper(principal);
    }
}
  1. CustomerRealm自定义Realm改造,改为从数据库中获取信息
/**
 * 自定义授权方法:获取当前登录用户权限信息,返回给Shiro用来进行授权对比
 * @param principalCollection
 * @return
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("进入自定义授权方法");
    //1.获取当前用户身份信息
    String principal = principalCollection.getPrimaryPrincipal().toString();
    //2.调用接口方法获取用户的角色信息(从数据库)
    List<String> roles = userService.getUserRoleInfo(principal);
    System.out.println("当前用户角色信息:" + roles);
    //3.创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //4. 存储角色
    info.addRole("admin");
    //3.返回
    return info;
}
  1. 访问:localhost:8080/LoginController/login 输入账号密码,点击测试授权,显示验证角色成功

在这里插入图片描述

1.3、授权-获取权限

  1. 创建数据库表
CREATE TABLE `permissions` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
 `name` VARCHAR(30) DEFAULT NULL COMMENT '权限名',
 `info` VARCHAR(30) DEFAULT NULL COMMENT '权限信息',
 `desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='权限表';

INSERT INTO `permissions` VALUES (1, '删除用户', 'user:delete', '删除用户');
INSERT INTO `permissions` VALUES (2, '新增用户', 'user:add', '新增用户');
INSERT INTO `permissions` VALUES (3, '修改用户', 'user:edit', '修改用户');

CREATE TABLE `role_ps` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
 `rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',
 `pid` BIGINT(20) DEFAULT NULL COMMENT '权限 id',
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色权限映射
表';
INSERT INTO `role_ps` VALUES (1, 1, 1);
INSERT INTO `role_ps` VALUES (2, 1, 2);
INSERT INTO `role_ps` VALUES (3, 1, 3);

在这里插入图片描述

  1. 根据角色名查询对应权限信息
SELECT info FROM permissions WHERE id IN (SELECT pid FROM role_ps WHERE rid 
IN (SELECT id FROM role WHERE NAME IN('admin','userMag')));
  1. mapper方法
/**
 * 根据角色名查询对应权限信息
 * @param roles
 * @return
 */
@Select({
        "<script>",
        "select info from permissions where id in",
        "(select pid from role_ps where rid in (",
        "select id from role where name in",
        "<foreach collection='roles' item='name' open='(' separator=',' close=')'>",
        "#{name}",
        "</foreach>",
        "))",
        "</script>"
})
List<String> getUserPermissionInfoMapper(@Param("roles")List<String> roles);
  1. Service接口
/**
* 根据用户获取用户角色的权限信息
* @return
*/
List<String> getUserPermissionInfo(List<String> roles);

实现类:

/**
 * 根据用户获取用户角色的权限信息
 */
@Override
public List<String> getUserPermissionInfo(List<String> roles) {
    return userMapper.getUserPermissionInfoMapper(roles);
}
  1. CustomerRealm自定义Realm改造
/**
 * 自定义授权方法:获取当前登录用户权限信息,返回给Shiro用来进行授权对比
 * @param principalCollection
 * @return
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("进入自定义授权方法");
    //1.获取当前用户身份信息
    String principal = principalCollection.getPrimaryPrincipal().toString();
    //2.调用接口方法获取用户的角色信息(从数据库)
    List<String> roles = userService.getUserRoleInfo(principal);
    System.out.println("当前用户角色信息:" + roles);
    //调用接口方法获取用户角色的权限信息
    List<String> permissions = userService.getUserPermissionInfo(roles);
    System.out.println("当前用户权限信息:" + permissions);
    //3.创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //4. 存储角色
    info.addRole("admin");
    //5. 存储权限
    info.addStringPermissions(permissions);
    //3.返回
    return info;
}
  1. 添加controller方法,注解设置角色权限
/**
 * 根据用户获取用户角色的权限信息,验证是否具有 user:delete 权限
 * @return
 */
@RequiresPermissions("user:delete")
@GetMapping("userPermissions")
@ResponseBody
public String userLoginPermissions() {
    System.out.println("登录认证验证权限");
    return "验证权限成功";
}
  1. 改造main.html
<h1>Shiro 登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
<a href="/logout">退出登录</a>
<br>
<a href="/LoginController/userLoginRoles">测试授权-角色验证</a>
<a href="/LoginController/userPermissions">测试授权-权限验证</a>
  1. 访问:localhost:8080/LoginController/login 输入账号密码,点击测试授权-权限授权,

在这里插入图片描述

1.4、授权-异常处理

  • 创建认证异常处理类

    src/main/java/com/kuang/shiro/controller/PermissionsException.java

    使用@ControllerAdvice@ExceptionHandler实现特殊异常处理。

@ControllerAdvice
public class PermissionsException {

    @ResponseBody
    @ExceptionHandler(UnauthorizedException.class)
    public String unauthorizedException(Exception e){
        return "无权限";
    }

    @ResponseBody
    @ExceptionHandler(AuthorizationException.class)
    public String authorizationException(Exception e){
        return "权限认证失败";
    }
}

这样就会在网页直接显示无权限,权限认证失败,而不是一堆异常。但是我们思考一个问题,我们如果展示了所有功能,只有部分用户可以点进来查看,其他没有权限的用户点进来是无权限,这显然体验感不好,我们要求对不同权限用户展示不同的功能。

1.5、前端页面授权

  1. 添加依赖
<!--配置 Thymeleaf 与 Shiro 的整合依赖-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.1.0</version>
</dependency>
  1. ShiroConfig添加新配置
/**
 * 用于解析thymeleaf中的  shiro:相关属性
 */
@Bean
public ShiroDialect shiroDialect() {
    return new ShiroDialect();
}
  1. Thymeleaf中常用的 shiro:属性
<!--guest标签:用户没有身份验证时显示相应信息,即游客访问信息-->
<shiro:guest>
</shiro:guest>

<!--user标签:用户已经身份验证/记住我登录后显示相应的信息-->
<shiro:user>
</shiro:user>

<!--authenticated标签:用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的-->
<shiro:authenticated>
</shiro:authenticated>

<!--notAuthenticated标签:用户已经身份验证通过,即没有调用 Subject.login 进行登录,包括记住我自动登录的也属于未进行身份验证-->
<shiro:notAuthenticated>
</shiro:notAuthenticated>

<!--principal标签:相当于((User)Subject.getPrincipals()).getUsername()-->
<shiro:principal>
</shiro:principal>

<!--hasRole标签:如果当前 Subject 有角色将显示 body 体内容-->
<shiro:hasRole name="admin">
</shiro:hasRole>

<!--hasAnyRoles标签:如果当前 Subject 有任意一个角色(或的关系)将显示 body 体内容-->
<shiro:hasAnyRoles name="admin,user">
</shiro:hasAnyRoles>

<!--hasPermission标签:如果当前 Subject 有权限将显示 body 体内容-->
<shiro:hasPermission name="user:create">
</shiro:hasPermission>
  1. 改造main.html
<!DOCTYPE html>
<!--导入Shiro的命名空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Shiro 登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
<a href="/logout">退出登录</a>
<br>
<!--如果有admin的角色,则展示这个a链接-->
<a href="/LoginController/userLoginRoles" shiro:hasRole="admin">测试授权-角色验证</a>
<!--如果有user:delete权限,则展示这个a链接-->
<a href="/LoginController/userPermissions" shiro:hasPermission="user:delete">测试授权-权限验证</a>
</body>
  1. 测试
    • 张三有admin角色,user:delete权限,则展示两个 a 标签
    • 李四只有admin角色,没有user:delete权限,则展示 测试授权-角色验证 标签

2、EhCache

EhCache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。可以和大部分Java项目无缝整合。EhCache支持内存和磁盘存储,默认存储在内存中,如内存不够时把缓存数据同步到磁盘中。EhCache支持基于Filter的Cache实现,也支持Gzip压缩算法。

  • EhCache直接在JVM虚拟机中缓存,速度快,效率高
  • EhCache缺点是缓存共享麻烦,集群分布式应用使用不方便

2.1、Shiro整合EhCache

Shiro官方提供了shiro-ehcache,实现了整合EhCache作为Shiro的缓存工具。可以缓存认证执行的Realm方法,减少对数据库的访问,提高认证效率。

  1. 添加依赖
<!--Shiro整合EhCache-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.13.0</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.13.0</version>
</dependency>
  1. 在 resources 下添加配置文件 ehcache/ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ehcache" updateCheck="false">
    <!--磁盘的缓存位置-->
    <diskStore path="java.io.tmpdir"/>
    <!--默认缓存-->
    <defaultCache
            maxEntriesLocalHeap="1000"
            eternal="false"
            timeToIdleSeconds="3600"
            timeToLiveSeconds="3600"
            overflowToDisk="false">
    </defaultCache>
    <!--登录认证信息缓存:缓存用户角色权限-->
    <cache name="loginRolePsCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true"/>
</ehcache>
  1. 修改配置类ShiroConfig
/**
 * 配置 SecurityManager
 */
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
    //1.创建defaultWebSecurityManager对象
    DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    //2.创建加密对象,并设置相关属性
    HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    // 2.1 采用md5加密
    matcher.setHashAlgorithmName("md5");
    // 2.2 迭代加密次数
    matcher.setHashIterations(1024);
    //3.将加密对象存储到CustomerRealm中
    customerRealm.setCredentialsMatcher(matcher);
    //4.将CustomerRealm存储到defaultWebSecurityManager中
    defaultWebSecurityManager.setRealm(customerRealm);
    //4.1 设置RememberMe
    defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
    //5.设置缓存管理器
    defaultWebSecurityManager.setCacheManager(getEhCacheManager());
    //6.返回
    return defaultWebSecurityManager;

}


/**
 * 缓存管理器
 */
public EhCacheManager getEhCacheManager() {
    EhCacheManager ehCacheManager = new EhCacheManager();
    InputStream is = null;

    try {
        is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
    } catch (IOException e) {
        e.printStackTrace();
    }
    CacheManager cacheManager = new CacheManager(is);
    ehCacheManager.setCacheManager(cacheManager);
    return ehCacheManager;
}
  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生命是有光的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值