【Shiro】Shiro 的学习教程(三)之 SpringBoot 集成 Shiro

2024-09-09 05:12

本文主要是介绍【Shiro】Shiro 的学习教程(三)之 SpringBoot 集成 Shiro,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 1、环境准备
  • 2、引入 Shiro
  • 3、实现认证、退出
    • 3.1、使用死数据实现
    • 3.2、引入数据库,添加注册功能
        • 后端代码
        • 前端代码
    • 3.3、MD5、Salt 的认证流程
  • 4.、实现授权
    • 4.1、基于角色授权
    • 4.2、基于资源授权
  • 5、引入缓存
    • 5.1、EhCache 实现缓存
    • 5.2、集成 Redis 实现 Shiro 缓存

1、环境准备

新建一个 SpringBoot 工程,引入依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro -->
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version>1.5.3</version>
</dependency>

resources/static 目录下新建 pages,再新建两个 html 文件

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>首页</title>
</head>
<body><h1>首页</h1>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>登录页面</title>
</head>
<body><h1>登录页</h1>
</body>
</html>

添加路由控制器 RoutingController

@Controller
public class RoutingController {@GetMapping("/index")public String index() {return "pages/index.html";}@GetMapping("/login")public String login() {return "pages/login.html";}}

访问:http://localhost:8080/indexhttp://localhost:8080/login 正常访问 index.htmllogin.html 页面。

2、引入 Shiro

①:添加一个自定义 Realm:

public class UserRealm extends AuthorizingRealm {// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {return null;}}

暂不实现这两个方法

②:添加 Shiro 配置类 ShiroConfiguration

@Configuration
public class ShiroConfiguration {// 1.创建 shiroFilter,负责拦截所有请求@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//给filter设置安全管理器shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);//配置系统受限资源//配置系统公共资源Map<String,String> map = new HashMap<>();// authc 请求这个资源需要认证和授权map.put("/index", "authc");//默认认证界面路径shiroFilterFactoryBean.setLoginUrl("/login");shiroFilterFactoryBean.setFilterChainDefinitionMap(map);return shiroFilterFactoryBean;}//2.创建安全管理器@Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();//给安全管理器设置defaultWebSecurityManager.setRealm(realm);return defaultWebSecurityManager;}//3.创建自定义realm@Beanpublic Realm getRealm(){return new UserRealm();}}

看下面代码:

// authc 请求这个资源需要认证和授权
map.put("/index", "authc");

表示:访问 /index 路径,需要认证、授权;否则,无法访问


③:路由控制器 RoutingController

@Controller
public class RoutingController {@GetMapping("/index")public String index() {return "pages/index.html";}@GetMapping("/login")public String login() {return "pages/login.html";}
}

启动项目再次访问 http://localhost:8080/index,发现页面跳转到登录页面。

这就说明我们的项目中成功的引入了 Shiro

3、实现认证、退出

3.1、使用死数据实现

修改 login.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>登录页面</title>
</head>
<body><h1>登录页</h1><form action="/user/login" method="post">用户名:<input type="text" name="username" > <br/>密码  : <input type="text" name="password"> <br><input type="submit" value="登录"></form></body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>首页</title>
</head>
<body><h1>首页</h1><a href="/user/logout">退出用户</a>
</body>
</html>

UserController

@Controller
@RequestMapping("/user")
public class UserController {@PostMapping("/login")public String login(String username, String password) {try {// 执行登录操作Subject subject = SecurityUtils.getSubject();// 认证通过后直接跳转到 index.htmlsubject.login(new UsernamePasswordToken(username,password));return "redirect:/index";} catch (AuthenticationException e) {e.printStackTrace();// 如果认证失败仍然回到登录页面return "redirect:/login";}}@GetMapping("/logout")public String logout(){Subject subject = SecurityUtils.getSubject();subject.logout();// 退出后仍然会到登录页面return "redirect:/login";}}

UserRealm:实现认证

public class UserRealm extends AuthorizingRealm {// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String principal = (String)authenticationToken.getPrincipal();// TODO 模拟数据库返回的数据if("christy".equals(principal)){return new SimpleAuthenticationInfo(principal,"123456", this.getName());}return null;}}

输入的用户名是 christy,密码 123456,就可以认证通过进入到主页

3.2、引入数据库,添加注册功能

pom.xml 引入依赖:

<!-- mybatis plus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version>
</dependency>
<!-- Druid数据源 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version>
</dependency>
<!-- Mysql -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version>
</dependency>

yml 添加配置:

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/sb_shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTCusername: rootpassword: root# 监控统计拦截的filtersfilters: stat,wall,slf4j,config# 配置初始化大小/最小/最大initial-size: 5min-idle: 5max-active: 20# 获取连接等待超时时间max-wait: 60000# 间隔多久进行一次检测,检测需要关闭的空闲连接time-between-eviction-runs-millis: 60000# 一个连接在池中最小生存的时间min-evictable-idle-time-millis: 300000validation-query: SELECT 'x'test-while-idle: truetest-on-borrow: falsetest-on-return: false# 打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为falsepool-prepared-statements: falsemax-pool-prepared-statement-per-connection-size: 20mybatis-plus:type-aliases-package: com.zzc.entityconfiguration:map-underscore-to-camel-case: true

sql 文件:

CREATE TABLE `t_user`  (`id` int NOT NULL AUTO_INCREMENT,`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`salt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`age` int NULL DEFAULT NULL,`email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
后端代码

UserController:添加一个注册接口

@Autowired
private UserService userService;@PostMapping("/register")
public String register(User user) {try {userService.register(user);return "redirect:/login";} catch (Exception e) {return "redirect:/register";}
}

UserServiceImpl:未写接口

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic void register(User user) {// 生成随机盐String salt = SaltUtil.getSalt(8);// 保存随机盐user.setSalt(salt);// 生成密码Md5Hash password = new Md5Hash(user.getPassword(), salt, 1024);// 保存密码user.setPassword(password.toHex());save(user);}}

UserMapper

public interface UserMapper extends BaseMapper<User> {
}

User

@Data
@TableName("t_user")
public class User {@TableId(type = IdType.AUTO)private Integer id;// 用户名@TableField(fill = FieldFill.INSERT_UPDATE)private String username;// 密码@TableField(fill = FieldFill.INSERT_UPDATE)private String password;// 盐@TableField(fill = FieldFill.INSERT)private String salt;// 年龄@TableField(fill = FieldFill.INSERT_UPDATE)private Integer age;// 邮箱@TableField(fill = FieldFill.INSERT_UPDATE)private String email;// 地址@TableField(fill = FieldFill.INSERT_UPDATE)private String address;
}

RoutingController:路由

@GetMapping("/register")
public String register() {return "pages/register.html";
}

ShiroConfiguration:过滤掉部分接口

Map<String,String> map = new HashMap<>();
// authc 请求这个资源需要认证和授权
map.put("/login", "anon");
map.put("/register", "anon");
map.put("/user/register", "anon");
map.put("/user/login", "anon");
map.put("/index", "authc");
前端代码

添加一个注册页面 register.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>注册页面</title>
</head>
<body>
<form action="/user/register" method="post">用户名:<input type="text" name="username" > <br/>密码  : <input type="text" name="password"> <br><input type="submit" value="立即注册">
</form>
</body>
</html>

启动项目,在注册页面进行注册

在这里插入图片描述

可以看到我们注册的用户已经顺利保存到数据库,而且密码是经过加密的

3.3、MD5、Salt 的认证流程

上面我们完成了基于 MD5+Salt 的注册流程,保存到数据库的密码都是经过加密处理的,这时候再用最初的简单密码匹配器进行 equals() 方法进行登录显然是不行的了,我们下面来改造一下认证的流程

UserRealm:修改认证(从数据库中获取)

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String principal = (String)authenticationToken.getPrincipal();// 由于 UserRealm 并没有交由工厂管理,故不能注入 UserServiceUserService userService = ApplicationContextUtil.getBean(UserService.class);// 数据库返回的数据User user = userService.findByUsername(principal);if (Objects.nonNull(user)) {return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), new CustomerByteSource(user.getSalt()), this.getName());}return null;
}

ShiroConfiguration:修改 Reaml 配置(添加密码配置器)

@Bean
public Realm getRealm(){UserRealm userRealm = new UserRealm();// 设置密码匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();// 设置加密方式credentialsMatcher.setHashAlgorithmName("MD5");// 设置散列次数credentialsMatcher.setHashIterations(1024);userRealm.setCredentialsMatcher(credentialsMatcher);return userRealm;
}

ApplicationContextUtil:获取 Spring 的 bean

@Component
public class ApplicationContextUtil implements ApplicationContextAware {public static ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}public static <T> T getBean(Class<T> clazz){return context.getBean(clazz);}}

CustomerByteSource

public class CustomerByteSource implements ByteSource, Serializable {private byte[] bytes;private String cachedHex;private String cachedBase64;public CustomerByteSource() {}public CustomerByteSource(byte[] bytes) {this.bytes = bytes;}public CustomerByteSource(char[] chars) {this.bytes = CodecSupport.toBytes(chars);}public CustomerByteSource(String string) {this.bytes = CodecSupport.toBytes(string);}public CustomerByteSource(ByteSource source) {this.bytes = source.getBytes();}public CustomerByteSource(File file) {this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(file);}public CustomerByteSource(InputStream stream) {this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(stream);}public static boolean isCompatible(Object o) {return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;}@Overridepublic byte[] getBytes() {return this.bytes;}@Overridepublic boolean isEmpty() {return this.bytes == null || this.bytes.length == 0;}@Overridepublic String toHex() {if (this.cachedHex == null) {this.cachedHex = Hex.encodeToString(this.getBytes());}return this.cachedHex;}@Overridepublic String toBase64() {if (this.cachedBase64 == null) {this.cachedBase64 = Base64.encodeToString(this.getBytes());}return this.cachedBase64;}@Overridepublic String toString() {return this.toBase64();}@Overridepublic int hashCode() {return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;}@Overridepublic boolean equals(Object o) {if (o == this) {return true;} else if (o instanceof ByteSource) {ByteSource bs = (ByteSource) o;return Arrays.equals(this.getBytes(), bs.getBytes());} else {return false;}}private static final class BytesHelper extends CodecSupport {private BytesHelper() {}public byte[] getBytes(File file) {return this.toBytes(file);}public byte[] getBytes(InputStream stream) {return this.toBytes(stream);}}}

重启项目进行测试

4.、实现授权

4.1、基于角色授权

sql 文件:

CREATE TABLE `t_role`  (`id` int NOT NULL,`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_role` VALUES (1, 'admin');
INSERT INTO `t_role` VALUES (2, 'user');CREATE TABLE `t_user_role_ref`  (`id` int NOT NULL,`user_id` int NULL DEFAULT NULL,`role_id` int NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_user_role_ref` VALUES (1, 1, 1);
INSERT INTO `t_user_role_ref` VALUES (2, 2, 2);

Role

@Data
@TableName("t_role")
public class Role {@TableId(type = IdType.AUTO)private Integer id;// 角色名@TableField(fill = FieldFill.INSERT_UPDATE)private String name;
}

UserRoleRef

@Data
@TableName("t_user_role_ref")
public class UserRoleRef {@TableId(type = IdType.AUTO)private Integer id;// 用户Id@TableField(fill = FieldFill.INSERT_UPDATE)private Integer userId;// 角色id@TableField(fill = FieldFill.INSERT_UPDATE)private Integer roleId;
}

UserMapper

public interface UserMapper extends BaseMapper<User> {
}

UserRoleRefMapper

public interface UserRoleRefMapper extends BaseMapper<UserRoleRef> {
}

RoleServiceImpl

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {@Autowiredprivate UserRoleRefMapper userRoleRefMapper;@Overridepublic List<Role> listByUserId(Integer userId) {QueryWrapper<UserRoleRef> wrapper = new QueryWrapper<>();wrapper.eq("user_id", userId);List<UserRoleRef> userRoleRefs = userRoleRefMapper.selectList(wrapper);List<Role> roles = new ArrayList<>();if (CollectionUtils.isNotEmpty(userRoleRefs)) {List<Integer> roleIds = userRoleRefs.stream().map(UserRoleRef::getRoleId).distinct().collect(Collectors.toList());QueryWrapper<Role> queryWrapper = new QueryWrapper<>();queryWrapper.in("id", roleIds);roles = baseMapper.selectList(queryWrapper);}return roles;}}

UserRealm:实现授权

public class UserRealm extends AuthorizingRealm {// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {UserService userService = ApplicationContextUtil.getBean(UserService.class);RoleService roleService = ApplicationContextUtil.getBean(RoleService.class);String principal = (String) principalCollection.getPrimaryPrincipal();User user = userService.findByUsername(principal);if (Objects.nonNull(user)) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();List<Role> roles = roleService.listByUserId(user.getId());if (CollectionUtils.isNotEmpty(roles)) {roles.forEach(role -> simpleAuthorizationInfo.addRole(role.getName()));}return simpleAuthorizationInfo;}return null;}
}

UserController:当前登录用户是什么角色就打印什么角色

@PostMapping("/login")
public String login(String username, String password) {try {// 执行登录操作Subject subject = SecurityUtils.getSubject();// 认证通过后直接跳转到 index.htmlsubject.login(new UsernamePasswordToken(username,password));// 授权if (subject.hasRole("admin")) {System.out.println("admin");} else if (subject.hasRole("user")) {System.out.println("user");}return "redirect:/index";} catch (AuthenticationException e) {e.printStackTrace();// 如果认证失败仍然回到登录页面return "redirect:/login";}
}

4.2、基于资源授权

sql 文件:

CREATE TABLE `t_permission`  (`id` int NOT NULL,`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_permission` VALUES (1, 'user:*:*', NULL);
INSERT INTO `t_permission` VALUES (2, 'order:*:*', NULL);
INSERT INTO `t_permission` VALUES (3, 'order:query:*', NULL);CREATE TABLE `t_role_permission_ref`  (`id` int NOT NULL,`role_id` int NULL DEFAULT NULL,`permission_id` int NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_role_permission_ref` VALUES (1, 1, 1);
INSERT INTO `t_role_permission_ref` VALUES (2, 1, 2);
INSERT INTO `t_role_permission_ref` VALUES (3, 2, 3);

Permission

@Data
@TableName("t_permission")
public class Permission {@TableId(type = IdType.AUTO)private Integer id;private String name;private String url;}

RolePermissionRef

@Data
@TableName("t_role_permission_ref")
public class RolePermissionRef {@TableId(type = IdType.AUTO)private Integer id;private Integer roleId;private Integer permissionId;}

PermissionMapper

public interface PermissionMapper extends BaseMapper<Permission> {
}

RolePermissionRefMapper

public interface RolePermissionRefMapper extends BaseMapper<RolePermissionRef> {
}

PermissionServiceImpl

@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService {@Autowiredprivate RolePermissionRefMapper rolePermissionRefMapper;@Overridepublic List<Permission> listByRoleIds(List<Integer> roleIds) {QueryWrapper<RolePermissionRef> wrapper = new QueryWrapper<>();wrapper.in("role_id", roleIds);List<RolePermissionRef> rolePermissionRefs = rolePermissionRefMapper.selectList(wrapper);List<Permission> permissions = new ArrayList<>();if (CollectionUtils.isNotEmpty(rolePermissionRefs)) {List<Integer> permissionIds = rolePermissionRefs.stream().map(RolePermissionRef::getPermissionId).distinct().collect(Collectors.toList());QueryWrapper<Permission> queryWrapper = new QueryWrapper<>();queryWrapper.in("id", permissionIds);permissions = baseMapper.selectList(queryWrapper);}return permissions;}}

UserRealm

public class UserRealm extends AuthorizingRealm {// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {UserService userService = ApplicationContextUtil.getBean(UserService.class);RoleService roleService = ApplicationContextUtil.getBean(RoleService.class);PermissionService permissionService = ApplicationContextUtil.getBean(PermissionService.class);String principal = (String) principalCollection.getPrimaryPrincipal();User user = userService.findByUsername(principal);if (Objects.nonNull(user)) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();// 角色List<Role> roles = roleService.listByUserId(user.getId());if (CollectionUtils.isNotEmpty(roles)) {roles.forEach(role -> simpleAuthorizationInfo.addRole(role.getName()));}// 资源List<Integer> roleIds = roles.stream().map(Role::getId).distinct().collect(Collectors.toList());List<Permission> permissions = permissionService.listByRoleIds(roleIds);if (CollectionUtils.isNotEmpty(permissions)) {permissions.forEach(permission -> simpleAuthorizationInfo.addStringPermission(permission.getName()));}return simpleAuthorizationInfo;}return null;}
}

UserController

@PostMapping("/login")
public String login(String username, String password) {try {// 执行登录操作Subject subject = SecurityUtils.getSubject();// 认证通过后直接跳转到 index.htmlsubject.login(new UsernamePasswordToken(username,password));// 角色-授权if (subject.hasRole("admin")) {System.out.println("admin");} else if (subject.hasRole("user")) {System.out.println("user");}// 资源-授权if (subject.isPermitted("order:*:*")) {System.out.println("order:*:*");} else if (subject.isPermitted("user:*:*")) {System.out.println("user:*:*");}return "redirect:/index";} catch (AuthenticationException e) {e.printStackTrace();// 如果认证失败仍然回到登录页面return "redirect:/login";}
}

重启项目测试

5、引入缓存

5.1、EhCache 实现缓存

Shiro 提供了缓存管理器,这样在用户第一次认证授权后访问其受限资源的时候就不用每次查询数据库从而达到减轻数据压力的作用,使用 Shiro 的缓存管理器也很简单

<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.5.3</version>
</dependency>

ShiroConfiguration

@Bean
public Realm getRealm(){UserRealm userRealm = new UserRealm();// 设置密码匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();// 设置加密方式credentialsMatcher.setHashAlgorithmName("MD5");// 设置散列次数credentialsMatcher.setHashIterations(1024);userRealm.setCredentialsMatcher(credentialsMatcher);// 设置缓存管理器userRealm.setCacheManager(new EhCacheManager());// 开启全局缓存userRealm.setCachingEnabled(true);// 开启认证缓存并指定缓存名称userRealm.setAuthenticationCachingEnabled(true);userRealm.setAuthenticationCacheName("authenticationCache");// 开启授权缓存并指定缓存名称userRealm.setAuthorizationCachingEnabled(true);userRealm.setAuthorizationCacheName("authorizationCache");return userRealm;
}

这样就将 EhCache 集成进来了,但是 shiro 的这个缓存是本地缓存,也就是说当程序宕机重启后仍然需要从数据库加载数据,不能实现分布式缓存的功能

5.2、集成 Redis 实现 Shiro 缓存

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

RedisCache

public class RedisCache<K,V> implements Cache<K,V> {private String cacheName;public RedisCache() {}public RedisCache(String cacheName) {this.cacheName = cacheName;}@Overridepublic V get(K k) throws CacheException {System.out.println("获取缓存:"+ k);return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());}@Overridepublic V put(K k, V v) throws CacheException {System.out.println("设置缓存key: "+k+" value:"+v);getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);return null;}@Overridepublic V remove(K k) throws CacheException {return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());}@Overridepublic void clear() throws CacheException {getRedisTemplate().delete(this.cacheName);}@Overridepublic int size() {return getRedisTemplate().opsForHash().size(this.cacheName).intValue();}@Overridepublic Set<K> keys() {return getRedisTemplate().opsForHash().keys(this.cacheName);}@Overridepublic Collection<V> values() {return getRedisTemplate().opsForHash().values(this.cacheName);}private RedisTemplate getRedisTemplate(){RedisTemplate redisTemplate = (RedisTemplate)ApplicationContextUtil.getBean("redisTemplate");redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());return redisTemplate;}
}

RedisCacheManager

public class RedisCacheManager implements CacheManager {@Overridepublic <K, V> Cache<K, V> getCache(String s) throws CacheException {System.out.println("缓存名称: "+ s);return new RedisCache<K,V>(s);}
}

ShiroConfiguration

// 设置缓存管理器
//userRealm.setCacheManager(new EhCacheManager());
userRealm.setCacheManager(new RedisCacheManager());

重启测试项目

一篇适合小白的Shiro教程

这篇关于【Shiro】Shiro 的学习教程(三)之 SpringBoot 集成 Shiro的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1150251

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06