springboot2.x以上整合shiro1.7.1和redis的一些注意事项
Posted 符华-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot2.x以上整合shiro1.7.1和redis的一些注意事项相关的知识,希望对你有一定的参考价值。
因为之前用的shiro版本一直是1.4.0,后来我们老大跟我说版本太低了,不安全,叫我升级到1.7.1。升级后发现有些配置很不一样的,需要注意一下。
一、pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/>
</parent>
<!-- ...... -->
<dependencies>
<!-- ...... -->
<!-- Shiro 核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Shiro-redis插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.3.1</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
二、关于ShiroFilterFactoryBean
shiro升级到1.7.1版本后,中文路径无法访问:比如上传的文件,文件名有中文时,无法访问文件,所以我们要自定义一个CustomShiroFilterFactoryBean,继承ShiroFilterFactoryBean
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.InvalidRequestFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.BeanInitializationException;
import javax.servlet.Filter;
import java.util.Map;
/**
* shiro升级到1.7.1版本,中文路径无法访问:比如上传的文件名有中文,导致无法访问文件
*/
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean {
@Override
public Class<MySpringShiroFilter> getObjectType()
{
return MySpringShiroFilter.class;
}
@Override
protected AbstractShiroFilter createInstance() throws Exception {
SecurityManager securityManager = getSecurityManager();
if (securityManager == null){
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)){
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
// Expose the constructed FilterChainManager by first wrapping it in a
// FilterChainResolver implementation. The AbstractShiroFilter implementations
// do not know about FilterChainManagers - only resolvers:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
Map<String, Filter> filterMap = manager.getFilters();
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
if (invalidRequestFilter instanceof InvalidRequestFilter){
// 此处是关键,设置false跳过URL携带中文400,servletPath中文校验bug
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
}
// Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
// FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
// here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
// injection of the SecurityManager and FilterChainResolver:
return new MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
private static final class MySpringShiroFilter extends AbstractShiroFilter{
protected MySpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver){
if (webSecurityManager == null){
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}else{
this.setSecurityManager(webSecurityManager);
if (resolver != null){
this.setFilterChainResolver(resolver);
}
}
}
}
}
三、RedisConfig
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.lang.reflect.Method;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* 定义缓存数据 key 生成策略的bean 包名+类名+方法名+所有参数
*/
@Bean
public KeyGenerator wiselyKeyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* springboot 2.0RedisCacheManager没有解析RedisTemplate报错
* RedisCacheManager取消了1.0版本中的public RedisCacheManager(RedisOperations redisOperations)的这个构造方法,所以我们无法再用RedisTemplate作为参数来自定义CacheManager。
* */
@Bean("ehCacheManager")
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置CacheManager的值序列化方式为json序列化
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
.fromSerializer(jsonSerializer);
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(pair);
defaultCacheConfig.entryTtl(Duration.ofSeconds(3600));
//初始化RedisCacheManager
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
/**
* redis序列化
* 1.项目启动时此方法先被注册成bean被spring管理,如果没有这个bean,则redis可视化工具中的中文内容(key或者value)都会以二进制存储,不易检查。
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//我们为了自己开发使用方便,一般使用<String, Object>类型
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//序列化配置
//json序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om=new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String序列化
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
//key使用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也使用String序列化
template.setHashKeySerializer(stringRedisSerializer);
//value使用json序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value使用json序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
四、ShiroConfig
import com.common.shiro.ShiroLoginFilter;
import com.common.shiro.ShiroRealm;
import com.common.shiro.ShiroSessionIdGenerator;
import com.common.shiro.ShiroSessionManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
/**
* 开启Shiro-aop注解支持
* @Attention 使用代理方式所以需要开启代码支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro基础配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 注意过滤器配置顺序不能颠倒
// 配置过滤:不会被拦截的链接
filterChainDefinitionMap.put("/doc.html", "anon");
filterChainDefinitionMap.put("/swagger/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/upload/**", "anon");
filterChainDefinitionMap.put("/api/sys/login", "anon");
filterChainDefinitionMap.put("/api/sys/getCode", "anon");
filterChainDefinitionMap.put("/**", "authc");
//获取filters
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
//将自定义 的FormAuthenticationFilter注入shiroFilter中
filters.put("authc", new ShiroLoginFilter());
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 密码加密
*/
@Bean
public HashedCredentialsMatcher credentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("SHA256");
// true 密码加密用hex编码; false 用base64编码
credentialsMatcher.setStoredCredentialsHexEncoded(false);
// 密码加密时循环的次数,如果密码只加密一次,这句注释即可;
//credentialsMatcher.setHashIterations(15);
return credentialsMatcher;
}
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义Session管理
securityManager.setSessionManager(sessionManager());
// 自定义Realm验证
securityManager.setRealm(realm());
return securityManager;
}
/**
* 配置Redis管理器,shiro-redis插件
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
//这里设置host时,必须要将端口号一起加上,因为shiro1.7.1没有setPort()
redisManager.setHost(host+":6379");
//这句没了
//redisManager.setPort(port);
redisManager.setTimeout以上是关于springboot2.x以上整合shiro1.7.1和redis的一些注意事项的主要内容,如果未能解决你的问题,请参考以下文章