专栏名称: Java基基
一个苦练基本功的 Java 公众号,所以取名 Java 基基
目录
相关文章推荐
煲都黎川  ·  重奖!江西举报食品问题最高奖100万! ·  昨天  
新京报评论  ·  几十块钱的“量子鞋垫”,哪有什么高科技 | ... ·  2 天前  
中国食品药品监管杂志  ·  论点摘编 | ... ·  2 天前  
51好读  ›  专栏  ›  Java基基

史上最全的微服务权限控制方案,完美实现!

Java基基  · 公众号  ·  · 2024-03-30 18:44

正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本
来源:blog.csdn.net/to10086/
article/details/109573948

一、微服务权限设计

先说下为什么写这篇文章,因为实际项目需要,需要对我们现在项目页面小到每个部件都要做权限控制,然后查了下网上常用的权限框架,一个是shrio,一个是spring security,看了下对比,都说shrio比较轻量,比较好用。

本文我们也选择了shrio来做整个项目的权限框架,同时结合网上大佬做过的一些spring boot+shrio整合案例,只能说大家图都画的挺好的…,看着大家的功能流程图仔细想想是那么回事,然后自己再实践就走不动了,各种坑都有啊。。。

回归到具体实现真的是步步都是坑。在实践的过程中想了下面几种方案,有些要么是还没开始coding就已经想着走不通了,有些就是代码敲了一半了发现行不通了,在本项目中我也参考了RCBA权限设计模型。

1、将shrio和网关gateway放在同一个服务中,但是这就带来一个问题,众所周知,shrio的数据中心realm需要用到用户服务当中的数据(查询用户、角色、权限之间的关系及数据),因此这里shrio就需要使用服务发现组件(我这里用的dubbo)去发现用户服务,但是用户服务中的登录又需要用到shrio的认证,到这里可能有人要说了,可以在用户服务中再去远程调用shrio服务啊,如果这种方法可以的话大家就可以用这种方法就不用往下看了…所以这就造成两个服务耦合在一块儿去了,这种方法直接pass掉。

2、在每一个服务中都共享一个shrio配置模块,这种方式同样也有问题,和上面出现的问题类似,现在shrio是个单独的模块,需要用到用户服务,可以使用dubbo远程调用,而用户服务需要将shrio配置模块通过maven导入进来,现在启动用户服务,肯定会报错:在shrio配置模块中没有找到服务的提供者。因此这种方案也可以pass掉了。

相信上面两种方案肯定不止我一个人这么做过,只能说shrio还是适合单体架构啊…当然,也不是说shrio不能做微服务的权限控制,在经过我长达一周的钻研和尝试之后,终于还是发现微服务用shrio怎样做权限设计了,下面说一下我的方案。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、设计方案

结合上面两种行不通的方法,我们取长补短,新的方案如下。

方案一

既然用户服务和shrio模块需要分开但是两者又是需要互相依赖,我们可以针对用户服务专门配置一个shrio模块,其他服务共享一个shrio模块。当然这两个shrio模块需要共享session会话

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

三、具体实现

示例项目使用springboot+mysql+mybatis-plus实现,服务发现和注册工具采用dubbo+zookeeper(这里我主要是想学习下这两个组件的用法,大家也可以使用eureka+feign)。

3.1 项目的结构如下:

  • common模块: 整个项目的公共模块, common-core 就包含了其他微服务需要的一些常量数据、返回值、异常, common-cache 模块中包含了所有微服务需要的shrio缓存配置,除了用户服务其他服务需要的授权模块 common-auth
  • gateway-service服务: 网关服务,所有其他服务的入口。
  • user-api: 用户服务定义的数据接口。
  • user-provider-service: 用户服务接口的实现,用户服务的提供者。
  • user-consumer-service: 用户服务的最外层,供nginx访问调用的服务,用户服务的消费者。
  • video-api: 同用户服务api。
  • video-provider: 同用户服务provider。
  • video-consumer: 同用户服务consumer。

3.2 表关系如下

3.3 共享session会话(缓存模块common-cache)

3.3.1 为什么需要共享session?

因为我们的项目是由多个微服务组成,当用户服务接收到用户的登录请求并登录成功时我们给用户返回一个sessionId并保存在用户的浏览器中的cookie里,用户此时再请求用户服务就会携带cookie当中的sessionId而服务器端就可以根据用户携带的sessionId取出保存在服务器的用户信息。

但是此时如果用户去请求视频服务就不能取出保存在服务器的用户信息,因为视频服务根本就不知道你是否登录过,所以这就需要我们将登录成功的用户信息进行共享而不仅仅是用户服务才可以访问。

3.3.2 怎么实现共享session?

我们在写shrio的相关配置时,都知道需要自定义shrio的安全管理器,也就是重写 DefaultWebSecurityManager ,我们看一下实例化这个安全管理器类中间有哪些组件会被初始化。

首先是 DefaultWebSecurityManager 的构造器。

public DefaultWebSecurityManager() {
    super();
    ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
    this.sessionMode = HTTP_SESSION_MODE;
    setSubjectFactory(new DefaultWebSubjectFactory());
    setRememberMeManager(new CookieRememberMeManager());
    setSessionManager(new ServletContainerSessionManager());
}

进入 DefaultWebSecurityManager 的父类 DefaultSecurityManager ,查看 DefaultSecurityManager 的构造器。

public DefaultSecurityManager() {
    super();
    this.subjectFactory = new DefaultSubjectFactory();
    this.subjectDAO = new DefaultSubjectDAO();
}

进入 DefaultSecurityManager 的父类 SessionsSecurityManager ,查看 SessionsSecurityManager 的构造器。

public SessionsSecurityManager() {
    super();
    this.sessionManager = new DefaultSessionManager();
    applyCacheManagerToSessionManager();
}

在这个构造器中我们看到了实例化了一个默认的session管理器 DefaultSessionManager 。我们点进去看看。可以看到 DefaultSessionManager 中默认的就是使用的是内存来保存session( MemorySessionDAO 就是对session进行操作的类)。

public DefaultSessionManager() {
    this.deleteInvalidSessions = true;
    this.sessionFactory = new SimpleSessionFactory();
    this.sessionDAO = new MemorySessionDAO();
}

根据上面我们的分析,如果要想在各个微服务中共享session就不能把session放在某个微服务所在服务器的内存中,需要把session单独拿出来共享,因此我们就需要写一个自定义的 SessionDAO 来覆盖默认的 MemorySessionDAO ,下面来看看怎么实现自定义的 SessionDAO

根据上面sessionDAO关系图我们可以知道, AbstractSessionDAO 主要有两个子类,一个是已经实现好的 EnterpriseCacheSessionDAO ,另一个就是 MemorySessionDAO ,现在我们需要替换默认的 MemorySessionDAO ,要么我们继承 AbstractSessionDAO 实现其中的读写session的方法,要么直接使用它已经给我们实现好的 EnterpriseCacheSessionDAO

在这里我选择直接使用 EnterpriseCacheSessionDAO 类。

public EnterpriseCacheSessionDAO() {
    setCacheManager(new AbstractCacheManager() {
        @Override
        protected Cache createCache(String name) throws CacheException {
            return new MapCache(name, new ConcurrentHashMap());
        }
    });
}

不过在上面类的构造方法中我们可以发现它默认是给我们new了一个 AbstractCacheManager 缓存管理器,并且使用的是 ConcurrentHashMap 来保存会话session,因此如果我们要用这个 EnterpriseCacheSessionDAO 类来实现缓存操作,那么我们就需要需要写一个自定义的 CacheManager 来覆盖它默认的 CacheManager

3.3.3 具体实现
  • 首先导入我们需要的依赖包
<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
        <exclusions>
            <exclusion>
                <groupId>io.lettucegroupId>
                <artifactId>lettuce-coreartifactId>
            exclusion>
        exclusions>
    dependency>
    <dependency>
        <groupId>redis.clientsgroupId>
        <artifactId>jedisartifactId>
    dependency>
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-pool2artifactId>
    dependency>
    
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-coreartifactId>
        <version>1.4.0version>
    dependency>
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-springartifactId>
        <version>1.4.0version>
    dependency>

dependencies>
  • 编写我们自己的CacheManager
@Component("myCacheManager")
public class MyCacheManager implements CacheManager {

    @Override
    public  Cache getCache(String s) throws CacheException {
        return new MyCache();
    }

}
  • Jedis客户端

这里不用RedisTemplate,因为经过实际测试和网上查阅资料RedisTemplate的查询效率远不如Jedis客户端。

public




    
 class JedisClient {
    private static Logger logger = LoggerFactory.getLogger(JedisClient.class);

    protected static final ThreadLocal threadLocalJedis = new ThreadLocal();
    private static JedisPool jedisPool;
    private static final String HOST = "localhost";
    private static final int PORT = 6379;
    private static final String PASSWORD = "1234";
    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
    private static int MAX_IDLE = 16;
    //可用连接实例的最大数目,默认值为8;
    //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = -1;
    //超时时间
    private static final int TIMEOUT = 1000 * 5;
    //等待可用连接的最大时间,单位毫秒,默认值为-1。表示用不超时
    private static int MAX_WAIT = 1000 * 5;

    // 连接数据库(0-15)
    private static final int DATABASE = 2;

    static {
        initialPool();
    }

    public static JedisPool initialPool() {
        JedisPool jp = null;
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxIdle(MAX_IDLE);
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnCreate(true);
            config.setTestWhileIdle(true);
            config.setTestOnReturn(true);
            jp = new JedisPool(config, HOST, PORT, TIMEOUT, PASSWORD, DATABASE);
            jedisPool = jp;
            threadLocalJedis.set(getJedis());
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("redis服务器异常", e);
        }
        return jp;
    }

    /**
     * 获取jedis实例
     *
     * @return jedis
     */



    public static Jedis getJedis() {
        boolean success = false;
        Jedis jedis = null;
        int i = 0;
        while (!success) {
            i++;
            try {
                if (jedisPool != null) {
                    jedis = threadLocalJedis.get();
                    if (jedis == null) {
                        jedis = jedisPool.getResource();
                    } else {
                        if (!jedis.isConnected() && !jedis.getClient().isBroken()) {
                            threadLocalJedis.set(null);
                            jedis = jedisPool.getResource();
                        }
                        return jedis;
                    }

                } else {
                    throw new RuntimeException("redis连接池初始化失败");
                }
            } catch (Exception e) {
                logger.error(Thread.currentThread().getName() + "第" + i + "次获取失败");
                success = false;
                e.printStackTrace();
                logger.error("redis服务器异常", e);
            }
            if (jedis != null) {
                success = true;
            }
            if (i >= 10 && i 20) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (i >= 20 && i 30) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

            if (i >= 30 && i 40) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (i >= 40) {
                System.out.println("redis彻底连不上了~~~~(>_);
                return null;
            }

        }
        if (threadLocalJedis.get() == null) {
            threadLocalJedis.set(jedis);
        }
        return jedis;
    }

    /**
     * 设置key-value
     *
     * @param key
     * @param value
     */


    public static void setValue(byte[] key, byte[] value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.set(key, value);

        } catch (Exception e) {
            threadLocalJedis.set(null);
            logger.error("redis服务器异常", e);
            throw  new RuntimeException("redis服务器异常");
        } finally {
            if (jedis != null) {
                close(jedis);
            }
        }
    }

    /**
     * 设置key-value,过期时间
     *
     * @param key
     * @param value
     * @param seconds
     */

    public static void setValue(byte[] key, byte[] value, int seconds) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.setex(key, seconds, value);
        } catch (Exception e) {
            threadLocalJedis.set(null);
            logger.error("redis服务器异常", e);
            throw new RuntimeException("redis服务器异常");
        } finally {
            if (jedis != null) {
                close(jedis);
            }
        }
    }

    public static byte[] getValue(byte[] key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            if (jedis == null || !jedis.exists(key)) {
                return null;
            }
            return jedis.get(key);
        } catch (Exception e) {
            threadLocalJedis.set(null);
            logger.error("redis服务器异常", e);
            throw new RuntimeException("redis服务器异常");
        } finally {
            if (jedis != null) {
                close(jedis);
            }
        }
    }

    public static long delkey(byte[] key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            if (jedis == null || !jedis.exists(key)) {
                return 0;
            }
            return jedis.del(key);
        } catch (Exception e) {
            threadLocalJedis.set(null);
            logger.error("redis服务器异常", e);
            throw new RuntimeException("redis服务器异常");
        } finally {
            if (jedis != null) {
                close(jedis);
            }
        }
    }


    public static void close(Jedis jedis) {
        if (threadLocalJedis.get() == null && jedis != null) {
            jedis.close();
        }
    }

    public static void clear() {
        if (threadLocalJedis.get() == null) {
            return;
        }
        Set keys = threadLocalJedis.get().keys("*");
        keys.forEach(key -> delkey(key.getBytes()));
    }

}
  • 自定义我们自己的Cache实现类
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.session.mgt.SimpleSession;

import java.io.*;
import java.time.Duration;
import java.util.Collection;
import java.util.Set;


public class MyCache<SVimplements Cache<ObjectObject{


    //设置缓存的过期时间(30分钟)
    private Duration cacheExpireTime = Duration.ofMinutes(30);

    /**
     * 根据对应的key获取值value
     *
     * @param s
     * @return
     * @throws CacheException
     */

    @Override
    public Object get(Object s) throws CacheException {
        System.out.println("get()方法....");
        byte[] bytes = JedisClient.getValue(objectToBytes(s));
        return bytes == null ? null : (SimpleSession) bytesToObject(bytes);
    }

    /**
     * 将K-V保存到redis中
     * 注意:保存的value是string类型
     *
     * @param s
     * @param o
     * @return
     * @throws CacheException
     */


    @Override
    public Object put(Object s, Object o) throws CacheException {
        JedisClient.setValue(objectToBytes(s), objectToBytes(o), (int) cacheExpireTime.getSeconds());
        return s;
    }


    public byte[] objectToBytes(Object object) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] bytes = null;
        try {
            ObjectOutputStream op = new  ObjectOutputStream(outputStream);
            op.writeObject(object);
            bytes = outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }

    public Object bytesToObject(byte[] bytes) {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
        Object object = null;
        try {
            ObjectInputStream ois = new ObjectInputStream(inputStream);
            object = ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return object;
    }

    /**
     * 删除缓存,根据key
     *
     * @param s
     * @return
     * @throws CacheException
     */

    @Override
    public Object remove(Object s) throws CacheException {
        return JedisClient.delkey(objectToBytes(s));
    }

    /**
     * 清空所有的缓存
     *
     * @throws CacheException
     */


    @Override
    public void clear() throws CacheException {
        JedisClient.clear();
    }

    /**
     * 缓存的个数
     *
     * @return
     */

    @Override
    public int size() {
        return JedisClient.getJedis().dbSize().intValue();
//        return redisTemplate.getConnectionFactory().getConnection().dbSize().intValue();
    }

    @Override
    public Set keys() {
        return JedisClient.getJedis().keys("*");
    }

    @Override
    public Collection values() {
        return null;
    }

}

注意上面 objectToBytes bytesToObject 方法是先将session转换成字节数组然后再存到redis中,从redis拿出来也是将字节数组转换成session对象,否则会报错。这是因为shrio使用的是自己包的 simpleSession 类,而这个类中的字段都是transient,不能直接序列化,需要我们自己将每个对象转成字节数组才可以进行操作。

当然,如果我们使用的是 RedisTemplate ,在配置的时候我们就不用写这两个方法了,直接使用默认的JDK序列化方式即可。

private transient Serializable id;
private transient Date startTimestamp;
private transient Date stopTimestamp;
private transient Date lastAccessTime;
private transient long timeout;
private transient boolean expired;
private transient String host;
private transient Map attributes;

因为这里这个缓存模块是一个独立模块需要给其他微服务使用的,所以要想其他微服务可以自动配置我们自定义的缓存管理器 CacheManager 组件,我们还需要在resources文件夹下面新建一个文件夹 META-INF ,并在META-INF文件夹下面新建 spring.factories 文件。 spring.factories 中的内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.qzwang.common.cache.config.MyCacheManager

3.4 授权模块common-auth

  • 首先导入我们需要的依赖包
 
    
        com.qzwang
        user-dubbo-api
        1.0-SNAPSHOT
    

    
    
        com.gitee.reger
        spring-boot-starter-dubbo
        1.1.3
    

    
    
        com.qzwang
        common-cache
        1.0-SNAPSHOT
    


  • 自定义realm,实现对用户访问权限的校验

注意,这里只实现权限校验,不实现用户认证,所以用户认证 doGetAuthenticationInfo 方法直接返回null就行了。

import com.alibaba.dubbo.config.annotation.Reference;
import  com.qzwang.user.api.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {

    @Reference(version = "0.0.1")
    private UserService userService;
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        //获取用户名
        String userName = (String) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo();
        System.out.println("username=" + userName);
        //给用户设置角色
        authenticationInfo.setRoles(userService.selectRolesByUsername(userName));
        //给用户设置权限
        authenticationInfo.setStringPermissions(userService.selectPermissionByUsername(userName));

        return authenticationInfo;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        return null;
    }
}
  • shrio的配置中心,shrio的一些核心配置,包括shrio的安全管理器、过滤器都在这个类进行设置。
import com.qzwang.common.cache.config.MyCacheManager;
import com.qzwang.common.cache.config.MySessionDao;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
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.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {

    // ShiroFilterFactoryBean
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 拦截
        Map filterMap = new LinkedHashMap<>();
        filterMap.put("/**""authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //shiroFilterFactoryBean.setLoginUrl("/user/index");
        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    // DefaultWebSecurityManager
    // @Qualifier中可以直接是bean的方法名,也可以给bean设置一个name,比如@Bean(name="myRealm"),在@Qulifier中就可以通过name来获取这个bean
    @Bean(name = "SecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm,
                                                                  @Qualifier("myDefaultWebSessionManager") DefaultWebSessionManager defaultWebSessionManager,
                                                                  @Qualifier("myCacheManager") MyCacheManager myCacheManager) 
{
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联UserRealm
        securityManager.setRealm(userRealm);
        securityManager.setSessionManager(defaultWebSessionManager);
        securityManager.setCacheManager(myCacheManager);
        return securityManager;
    }

    // 创建Realm对象, 需要自定义类
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }


    /**
     * 下面DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor必须定义,
     * 否则不能使用@RequiresRoles@RequiresPermissions
     *
     * @return
     */

    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }


    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 设置自定义session管理器
     */

    @Bean
    public DefaultWebSessionManager myDefaultWebSessionManager(SimpleCookie simpleCookie) {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionIdCookie(simpleCookie);
        defaultWebSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
        return defaultWebSessionManager;
    }
    @Bean
    public SimpleCookie simpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("myCookie");
        simpleCookie.setPath("/");
        simpleCookie.setMaxAge(30);
        return simpleCookie;
    }
}

3.5 用户消费者服务user-consumer

先导入我们需要的依赖包。


    
        junit
        junit
        4.11
        test
    

    
        com.qzwang
        user-dubbo-api
        1.0-SNAPSHOT
    

    
    
        com.gitee.reger
        spring-boot-starter-dubbo
        1.1.3
    

    
        org.apache.zookeeper
        zookeeper
        3.6.2
    

    
        com.101tec
        zkclient
        0.11
    

    
    
    
        com.qzwang
        common-cache
        1.0-SNAPSHOT
    



这个服务的缓存用公共模块的缓存(common-cache),shrio配置需要用我们自己的配置,这里realm中的认证和授权我们都需要实现。

import com.alibaba.dubbo.config.annotation.Reference;
import com.qzwang.user.api.model.User;
import com.qzwang.user.api.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 org.springframework.stereotype.Component;

@Component
public class UserRealm extends AuthorizingRealm {

    @Reference(version = "0.0.1")
    private UserService userService;
    // 授权






请到「今天看啥」查看全文