栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

2021-10-14

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

2021-10-14

Shiro集成redis实现session共享
  • 一. 搭建redis基本环境
      • ①加入redis相关依赖
      • ②写入redis的application.yml配置文件
  • 二. shiro认证授权实现
      • ①导入shiro相关依赖
      • ②配置shiro
      • ③redis缓存管理器配置
      • ④redis缓存序列化方式配置
  • 三. 测试

一. 搭建redis基本环境 ①加入redis相关依赖
		
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
        
            org.springframework.session
            spring-session-data-redis
        
        
        
            org.springframework.session
            spring-session-core
        
②写入redis的application.yml配置文件
#配置连接redis的参数
spring:
  redis:
    cluster:
    #配置redis集群节点
      nodes: 92.168.10.140:8001,192.168.10.140:8002,192.168.10.140:8003,192.168.10.140:8004,192.168.10.140:8005,192.168.10.140:8006
  session:
    store-type: redis #存放session的存储方式
    redis:
      flush-mode: on_save #配置session刷新方式
    timeout: 30m #设置session超时时间

这样redis的基本环境就搭建完成了。

二. shiro认证授权实现 ①导入shiro相关依赖
		
            org.apache.shiro
            shiro-spring
            1.4.0
        
        
        
            org.apache.shiro
            shiro-web
            1.4.0
        
②配置shiro

Shiro 配置类-》ShiroConfig.java
–负责配置shiro的资源配置、安全管理器对象、和管理自定义的realm对象

@Configuration
public class ShiroConfig {

    //创建shiroFilter(shiro拦截器对象--负责拦截所有请求)
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        //给Filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
       
        //配置系统受限资源
        //配置系统公共资源
        Map map=new HashMap();
        
        
        
        shiroFilterFactoryBean.setLoginUrl("/login");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

    //创建安全管理对象
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置Realm
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }
    //创建自定义Realm
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        //修改凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置加密算法为Md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        //设置散列次数
        credentialsMatcher.setHashIterations(1024);

        customerRealm.setCredentialsMatcher(credentialsMatcher);

        //设置缓存管理器
        customerRealm.setCacheManager(new RedisCacheManager());
        //开启缓存
        customerRealm.setCachingEnabled(true);
        //开启认证缓存并指定缓存名称
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("AuthenticationCache");
        //开启授权缓存并指定缓存名称
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("AuthorizationCache");
        return customerRealm;
    }
}

自定义Realm-》 CustomerRealm.java
用来处理用户的认证与授权

public class CustomerRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//授权
        //获取身份信息
//        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
//        //根据身份信息查取数据库获取 权限 和 角色 信息
//        if(primaryPrincipal.equals("xwm")){  //(模拟操作)
//            SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
//            simpleAuthorizationInfo.addRole("admin");//为用户添加角色
//            simpleAuthorizationInfo.addStringPermission("admin:*:*");//admin角色下的所有资源的所有权限
//            return simpleAuthorizationInfo; //返回对象
//        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//认证
        String principal = (String)authenticationToken.getPrincipal();//获取用户名
        UserServiceImpl userService=(UserServiceImpl) ApplicationContextUtils.getContext("UserService");//获取容器里的Bean
        Patient patient = userService.loginUser(principal);//通过姓名获取用户信息
        if(patient!=null){//用户名,用户已加密的密码,盐,当前对象
            System.out.println("-将patient对象存入session-");//会同时将将patient对象存入session中
            return new SimpleAuthenticationInfo(patient,patient.getPatientpwd(),//密码
                    new CustomerByteSource(patient.getPsalt()),//盐
                    this.getName());//当前realm
        }
        return null;
    }
}

用来为用户加密产生加密盐值的工具类
可以产生随机的字符串,可以加强加密的安全性。

public class SaltUtils {
    public static String getSalt(int n){//快捷键:Ctrl+Shift+U==>可将选中的字母大写转小写
        char[] chars = "ABCDEFJHIJKLMNOPQRSTUVWXYZabcdefjhijklmnopqrstuvwxyz0987654321!@#$%^&*():?><|~/}{][`".toCharArray();
        StringBuilder str=new StringBuilder();
        for (int i = 0; i < n; i++) {
            char aChar = chars[new Random().nextInt(chars.length)];
            str.append(aChar);
        }
        return str.toString();
    }

    public static void main(String[] args) {//盐值生成测试
        String str=getSalt(8);
        System.out.println(str);
        Md5Hash md5Hash=new Md5Hash("123",str,1024);
        System.out.println(md5Hash.toHex());
    }
}

自定义盐值实现类(由于原本的盐实现是没有序列化的,在redis缓存过程中会出现序列化错误,所以需要自定义一个salt实现,实现序列化接口。)

//自定义salt实现  实现序列化接口
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;
    }

    @Override
    public byte[] getBytes() {
        return this.bytes;
    }

    @Override
    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    @Override
    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    @Override
    public String tobase64() {
        if (this.cachedbase64 == null) {
            this.cachedbase64 = base64.encodeToString(this.getBytes());
        }

        return this.cachedbase64;
    }

    @Override
    public String toString() {
        return this.tobase64();
    }

    @Override
    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    @Override
    public 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);
        }
    }
}

③redis缓存管理器配置

通过实现CacheManager接口为shiro配置redis缓存管理器。

public class RedisCacheManager implements CacheManager {
    @Override
    public  Cache getCache(String cacheName) throws CacheException {
        System.out.println("缓存名称: "+cacheName);
        return new RedisCache(cacheName);
    }
}

自定义redis缓存实现RedisCache类,内部都是操作redis数据库的方法。

//存储键值对不应该使用注解方式而应该从容器获取对象,因为这种方式只能存储值为字符串的键值对。

public class RedisCache implements Cache {
    private String cacheName;

    public RedisCache() {
    }

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public V get(K k) throws CacheException {
        System.out.println("获取缓存:"+ k);
        return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public 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;
    }

    @Override
    public V remove(K k) throws CacheException {
        System.out.println("移除缓存key: "+k);
        return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    private RedisTemplate getRedisTemplate(){//获取redis操作对象 RedisTemplate 
    	//通过工具类ApplicationContextUtils指定Bean名来获取容器中指定的Bean对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getContext("redisTemplate");
        return redisTemplate;
    }
}

用来从容器里获取Bean对象的工具类ApplicationContextUtils

@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override//系统会自动调用该类经工厂传如该函数(由程序自动调用)
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context=applicationContext;
    }

    //根据bean名字获取工厂里指定的bean对象
    public static Object getContext(String name){
        Object bean = context.getBean(name);
        return bean;
    }
}
④redis缓存序列化方式配置

redis配置类 用来配置redis个类型的序列化方式(如果不配置该项,程序会出现反序列化失败的错误)
注:在分布式项目里,如果想要这种方式来实现session共享则必须两端即双方的redis缓存序列化方式都必须一样否则程序会出现序列化的异常错误。从而使程序无法正常运行。

@Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
三. 测试

在分布式项目中,A模块内通过session.setAttribute("xxx",xxx)将数据存入redis中,再通过ModelAndView view = new ModelAndView(new RedirectView("请求地址"))来重定向到B模块,通过测试B模块的session.getAttribute("xxx")来判断共享是否成功,如果有值则代表session共享成功。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/328831.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号