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(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * 配置RedisSessionDAO
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
    
    
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        redisSessionDAO.setKeyPrefix("shiro:session:");
        redisSessionDAO.setExpire(timeout);
        return redisSessionDAO;
    }

    /**
     * 身份验证器
     */
    @Bean
    public ShiroRealm realm() {
    
    
        return new ShiroRealm();
    }

    /**
     * SessionID生成器
     */
    @Bean
    public ShiroSessionIdGenerator sessionIdGenerator(){
    
    
        return new ShiroSessionIdGenerator();
    }

    /**
     * 配置Session管理器
     */
    @Bean
    public SessionManager sessionManager() {
    
    
        ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
        shiroSessionManager.setSessionDAO(redisSessionDAO());
        shiroSessionManager.setSessionIdUrlRewritingEnabled(false);
        return shiroSessionManager;
    }
}

五、ShiroRealm

import com.common.util.ShiroUtil;
import com.entity.sys.SysMenu;
import com.entity.sys.SysUser;
import com.service.sys.SysMenuService;
import com.service.sys.SysUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * Shiro权限匹配和账号密码匹配
 */
public class ShiroRealm extends AuthorizingRealm {
    
    

    @Resource
    private SysUserService sysUserService;
    @Resource
    private SysMenuService sysMenuService;

    /**
     * 授权权限
     * 用户进行权限验证时候Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //这里可以进行授权和处理
        Set<String> rolesSet = new HashSet<>();
        Set<String> permsSet = new HashSet<>();
        rolesSet.add(ShiroUtil.userRoleKey());
        //查询角色和权限(这里根据业务自行查询)
        List<SysMenu> menuList = sysMenuService.selectMenuByRoleId(ShiroUtil.userRoleId());
        for (SysMenu menu :menuList) {
    
    
            permsSet.add(menu.getPerms());
        }
        //将查到的权限和角色分别传入authorizationInfo中
        authorizationInfo.setStringPermissions(permsSet);
        authorizationInfo.setRoles(rolesSet);
        return authorizationInfo;
    }

    /**
     * 身份认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //获取用户的输入的账号
        String loginName = token.getUsername();
        //因为我的密码没有用盐值加密,所以这里要获取用户输入的明文密码,通过ShiroConfig的密码加密方式去验证
        String password = new String(token.getPassword());

        SysUser user = sysUserService.getUserByName(loginName);
        //判断账号是否存在
        if (user == null ) {
    
    
            throw new AuthenticationException();
        }
        if (user.getState().intValue()==2){
    
    
            throw new LockedAccountException();
        }
        //进行验证
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user,
                password,//明文密码,通过 ShiroConfig.credentialsMatcher() 的密码加密方式去验证密码
                getName()
        );
        //验证成功开始踢人(清除缓存和Session)
        ShiroUtil.deleteCache(loginName);
        return authenticationInfo;
    }
}

多realm只要配置好对应的realm就行,主要要注意的是 cacheManagerredisManager

猜你喜欢

转载自blog.csdn.net/weixin_43165220/article/details/119609800
今日推荐