利用Shiro和Redis优化用户信息管理模块
引言
用户信息管理在很多应用中的核心功能之一,它关乎到数据的安全性,以及系统的性能和响应速度。为了提供更稳定、高效的用户信息管理服务,开发者们一直在不断探索和尝试优化的策略。在这篇博客中,我将详细介绍在我的最近的项目中,如何利用Apache Shiro和Redis来优化用户信息管理模块。
Apache Shiro简介
Apache Shiro是一个强大且易用的Java安全框架,提供了认证、授权、加密和会话管理等功能,为开发安全的应用程序提供了全面的保障。Shiro的架构简洁,易于理解和使用,同时具有高度的可扩展性。
Shiro的主要特性
- 认证: Shiro支持多种方式的认证,如:LDAP、数据库、CAS等。它还支持一次性验证和多次验证。
- 授权: Shiro支持基于角色的访问控制(RBAC)和基于权限的访问控制(PBAC)。它也支持细粒度的权限控制。
- 会话管理: Shiro可以管理用户的会话信息,包括会话的创建、过期、更新等。
- 加密: Shiro提供了多种加密算法,如MD5、SHA-256等。
Shiro的架构
Shiro的架构主要包括以下几个核心组件:
- Subject: 在Shiro中,Subject是一个安全相关的概念,代表了当前的用户。Subject可以是一个人,也可以是第三方服务、守护进程账户、时间调度任务等。
- Security Manager: 安全管理器是Shiro架构的核心,它是Shiro的实际运行环境。
- Realm: Realm是Shiro与应用安全数据之间的桥梁,也是真实的安全数据源,可以从数据库、LDAP、文本配置文件、Active Directory等获取。
Redis简介
Redis是一个开源的内存数据库,数据以键值对的形式存储,支持多种数据结构。由于Redis所有数据都存储在内存中,所以其读写速度非常快,可以作为缓存来使用。
Redis的主要特性
- 性能高: 由于所有数据都存储在内存中,Redis能提供高性能的读写操作。据统计,Redis每秒可以处理约110000次的读操作,和约81000次的写操作。
- 丰富的数据类型: Redis支持字符串、列表、集合、有序集合和哈希表等数据类型,满足各种应用场景的需求。
- 支持数据持久化: Redis提供了多种数据持久化方式,包括RDB快照和AOF日志。
- 支持数据过期: Redis可以为每个键设置过期时间,实现自动的数据管理。
Shiro在用户信息管理中的应用
在我们的项目中,我们使用Shiro进行用户的认证和授权处理。在用户登录时,Shiro会接收用户输入的用户名和密码,通过其内置的认证流程,对用户信息进行校验,如果用户名和密码匹配,用户就能够成功登录,否则会返回错误信息。此外,Shiro还可以通过定义一些角色和权限,对用户进行更精细的授权控制。
Shiro的认证流程
用户首先输入用户名和密码,Shiro会创建一个Token并将这些信息放入Token中,然后Shiro会将Token传递给SecurityManager,SecurityManager将Token传递给Authenticator,Authenticator通过Realm从数据库中获取用户的真实信息,然后将用户输入的信息和数据库中的信息进行比较,如果信息匹配,认证成功,否则认证失败。
Shiro的授权流程
Shiro的授权主要通过两种方式实现:基于角色的访问控制(RBAC)和基于权限的访问控制(PBAC)。在RBAC中,我们首先定义一些角色,然后为每个角色分配一些权限,最后我们将用户分配到某个角色,用户就能拥有该角色的所有权限。在PBAC中,我们直接为用户分配权限。
Redis在用户信息管理中的应用
我们使用Redis作为用户会话信息和常用用户信息的缓存,当用户登录成功后,我们将用户的会话信息和常用用户信息存储到Redis中,这样在后续的请求中,我们可以直接从Redis中获取这些信息,而不需要再次查询数据库,大大提高了应用的响应速度。同时,我们也利用Redis的过期策略,对用户会话进行有效期的管理。
用户会话信息的存储
当用户登录成功后,我们将用户的会话信息存储到Redis中,包括用户的ID、用户名、角色、权限等信息。我们将这些信息以键值对的形式存储在Redis中,键为用户的ID,值为用户的会话信息。这样,在后续的请求中,我们只需要根据用户的ID,就可以快速获取用户的会话信息。
用户会话信息的获取
当用户进行请求时,我们会首先从Redis中获取用户的会话信息,如果Redis中没有该用户的会话信息,我们再从数据库中获取。通过这种方式,我们大大减少了对数据库的访问,提高了应用的响应速度。
用户会话的有效期管理
我们利用Redis的过期策略,对用户会话进行有效期的管理。我们为每个用户的会话设置一个过期时间,当过期时间到达后,Redis会自动删除该用户的会话信息。这样,我们不仅可以防止用户的会话信息在Redis中占用过多的空间,还可以防止用户的会话信息被恶意利用。
项目中Shiro和Redis的整合
我们的项目后端基于SpringBoot,可以方便地整合Shiro和Redis。我们使用了SpringBoot的Shiro Starter和Redis Starter,只需要在application.yml中进行简单的配置,就可以实现Shiro和Redis的整合。我们将Shiro的会话管理改为使用Redis,这样可以实现分布式会话,让我们的应用更适应分布式环境。
Shiro和Redis的整合配置
我们首先在application.yml中配置Redis的相关信息,如主机名、端口、密码等。然后,我们在Shiro的配置文件中,将会话管理器的实现类设置为RedisSessionDAO。这样,Shiro就会使用Redis来管理用户的会话信息。
# mysql
spring:
# 环境 dev|test|pro
profiles:
active: prod
# jackson时间格式化
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
http:
multipart:
max-file-size: 100MB
max-request-size: 100MB
enabled: true
# 指定静态资源的路径
resources:
static-locations: classpath:/static/,classpath:/views/
redis:
open: false # 是否开启redis缓存 true开启 false关闭
database: 0
host: localhost
port: 6380
password: 123456 # 密码(默认为空)
timeout: 6000 # 连接超时时长(毫秒)
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
Shiro和Redis的整合使用
在整合了Shiro和Redis之后,我们的应用就可以同时利用Shiro的认证、授权功能和Redis的缓存功能。当用户登录时,我们通过Shiro进行认证,认证成功后,我们将用户的会话信息存储到Redis中。在后续的请求中,我们首先从Redis中获取用户的会话信息,如果获取失败,我们再通过Shiro进行认证。通过这种方式,我们大大提高了应用的响应速度。
package cn.jeefast.config;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import cn.jeefast.system.oauth2.OAuth2Filter;
import cn.jeefast.system.oauth2.OAuth2Realm;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Shiro配置
*
* @author theodo
* @email 36780272@qq.com
* @date 2017-10-20 18:33
*/
@Configuration
public class ShiroConfig {
@Bean("sessionManager")
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
//sessionManager.setSessionIdCookieEnabled(false);
return sessionManager;
}
@Bean("securityManager")
public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/api/**", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/**/*.css", "anon");
filterMap.put("/**/*.js", "anon");
filterMap.put("/**/*.html", "anon");
filterMap.put("/img/**", "anon");
filterMap.put("/fonts/**", "anon");
filterMap.put("/plugins/**", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/favicon.ico", "anon");
filterMap.put("/captcha.jpg", "anon");
filterMap.put("/sys/regsave", "anon");
filterMap.put("/scxxgs.html", "anon");
filterMap.put("/sysIndex/**", "anon");
filterMap.put("/upload/**", "anon");
// 文本编辑器
filterMap.put("/**/*.eot", "anon");
filterMap.put("/**/*.ttf", "anon");
filterMap.put("/**/*.woff", "anon");
filterMap.put("/", "anon");
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
package cn.jeefast.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置
*
* @author theodo
* @email 36780272@qq.com
* @date 2017-10-10 19:22
*/
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
总结
通过整合Shiro和Redis,我们优化了用户信息管理模块,提高了应用的性能,增强了应用的安全性。我希望我的这份经验分享能够对你有所帮助,如果你有任何问题或者想法,欢迎在下方评论区交流。在未来的工作中,我还将继续探索更多的优化策略,以提供更好的服务。