微服务权限控制方案详解

微服务权限控制方案详解

一、微服务权限设计

1.1 为什么选择Shiro?

Shiro是一个轻量级的Java安全框架,提供了认证、授权、加密和会话管理等功能。与Spring Security相比,Shiro的配置更加简单,适合快速开发和小型项目。在微服务架构中,Shiro的灵活性和轻量性使其成为一个理想的选择。

1.2 微服务权限设计的挑战

  1. 分布式环境:微服务环境下,多个服务分布在不同的服务器上,每个服务都需要独立进行认证和授权。
  2. 服务间调用:微服务之间需要通过API相互调用,这时如何确保跨服务的权限认证是一个挑战。
  3. 功能权限和数据权限的统一管理:功能权限主要控制用户能访问哪些功能模块,而数据权限则控制用户能访问哪些具体数据记录。

1.3 设计思路

  1. 用户服务与Shiro模块分离:用户服务负责用户认证,其他服务共享一个Shiro模块进行权限校验。
  2. Redis共享Session:通过Redis存储用户的会话信息,确保多个服务可以访问同一个用户的会话。
  3. 服务间的权限数据同步:通过Dubbo或其他RPC框架,实现用户服务和其他服务之间的权限数据同步。

二、具体实现

2.1 项目结构

项目的整体结构如下:

  • common模块:包含公共常量、返回值、异常等。
  • gateway-service:网关服务,所有其他服务的入口。
  • user-api:用户服务定义的数据接口。
  • user-provider-service:用户服务接口的实现。
  • user-consumer-service:用户服务的消费者。
  • video-api:视频服务定义的数据接口。
  • video-provider:视频服务接口的实现。
  • video-consumer:视频服务的消费者。

2.2 表关系

权限控制通常涉及以下表:

  • 用户表:存储用户的基本信息。
  • 角色表:存储角色信息。
  • 权限表:存储权限信息。
  • 用户角色表:存储用户和角色的关联关系。
  • 角色权限表:存储角色和权限的关联关系。

2.3 共享Session会话

2.3.1 为什么需要共享Session?

在微服务架构中,用户登录后,多个服务需要共享用户的会话信息。如果每个服务都独立存储用户的会话信息,会导致数据不一致和重复逻辑。通过共享Session,可以确保所有服务都能访问到用户的登录状态和权限信息。

2.3.2 如何实现共享Session?
  1. 自定义SessionDAO:覆盖Shiro默认的MemorySessionDAO,使用Redis存储Session。
  2. 自定义CacheManager:实现Shiro的CacheManager接口,使用Redis作为缓存存储。
java 复制代码
@Component("myCacheManager")
public class MyCacheManager implements CacheManager {

    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        return new MyCache<>();
    }
}
  1. Jedis客户端:使用Jedis连接Redis,实现Session的存储和读取。
csharp 复制代码
public class JedisClient {
    private static JedisPool jedisPool;

    static {
        initialPool();
    }

    public static Jedis getJedis() {
        // 获取Jedis实例
    }

    public static void setValue(byte[] key, byte[] value) {
        // 设置键值对
    }

    public static byte[] getValue(byte[] key) {
        // 获取键值对
    }
}
  1. 自定义Cache实现类:将Session对象转换为字节数组存储到Redis中。
vbnet 复制代码
public class MyCache<S, V> implements Cache<Object, Object> {

    @Override
    public Object get(Object key) throws CacheException {
        byte[] bytes = JedisClient.getValue(objectToBytes(key));
        return bytes == null ? null : (SimpleSession) bytesToObject(bytes);
    }

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

2.4 授权模块common-auth

2.4.1 自定义Realm

自定义Realm用于实现用户权限的校验。

scala 复制代码
public class UserRealm extends AuthorizingRealm {

    @Reference
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String userName = (String) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(userService.selectRolesByUsername(userName));
        info.setStringPermissions(userService.selectPermissionByUsername(userName));
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName = (String) token.getPrincipal();
        User user = userService.selectByUsername(userName);
        if (user != null) {
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
        }
        return null;
    }
}
2.4.2 Shiro配置

配置Shiro的核心组件,包括Realm、Session管理器、过滤器等。

typescript 复制代码
@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/testFunc", "authc");
        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm, DefaultWebSessionManager sessionManager, MyCacheManager cacheManager) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(userRealm);
        manager.setSessionManager(sessionManager);
        manager.setCacheManager(cacheManager);
        return manager;
    }

    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    @Bean
    public DefaultWebSessionManager myDefaultWebSessionManager(SimpleCookie simpleCookie) {
        DefaultWebSessionManager manager = new DefaultWebSessionManager();
        manager.setSessionDAO(new EnterpriseCacheSessionDAO());
        manager.setSessionIdCookie(simpleCookie);
        return manager;
    }

    @Bean
    public SimpleCookie simpleCookie() {
        SimpleCookie cookie = new SimpleCookie("myCookie");
        cookie.setPath("/");
        cookie.setMaxAge(30);
        return cookie;
    }
}

2.5 用户消费者服务user-consumer

2.5.1 用户登录接口

实现用户登录功能。

kotlin 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @Reference
    private UserService userService;

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public R login(@RequestBody User user) {
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return R.ok();
        } catch (Exception e) {
            return R.failed();
        }
    }

    @RequestMapping(value = "/unAuth", method = RequestMethod.GET)
    public R unAuth() {
        return R.failed("该用户未授权!");
    }

    @RequiresRoles("admin")
    @RequestMapping(value = "/testFunc", method = RequestMethod.GET)
    public R testFunc() {
        return R.ok("yes success!!!");
    }
}

2.6 视频消费者服务video-consumer

2.6.1 测试共享Session

验证不同服务间是否可以共享Session。

less 复制代码
@RestController
@RequestMapping("/video")
public class VideoController {

    @RequestMapping("/getVideo")
    @RequiresRoles("admin")
    public R getVideo() {
        return R.ok();
    }
}

三、测试结果

  1. 未登录访问接口:跳转到Shiro默认的登录页面。
  2. 登录后访问接口:成功访问受保护的接口。
  3. 权限校验:未授权用户访问受保护接口时,返回未授权提示。

四、面试回答思路和答案

4.1 为什么选择Shiro而不是Spring Security?

回答思路

  • Shiro的配置更加简单,适合快速开发和小型项目。
  • Shiro提供了灵活的会话管理和权限控制,适合微服务架构。
  • Spring Security功能强大但配置复杂,适合大型企业级应用。

答案

Shiro是一个轻量级的Java安全框架,提供了认证、授权、加密和会话管理等功能。与Spring Security相比,Shiro的配置更加简单,适合快速开发和小型项目。在微服务架构中,Shiro的灵活性和轻量性使其成为一个理想的选择。

4.2 如何实现Session共享?

回答思路

  • 使用Redis存储Session信息,确保多个服务可以访问同一个用户的会话。
  • 自定义SessionDAO覆盖Shiro默认的MemorySessionDAO。
  • 使用Jedis客户端连接Redis,实现Session的存储和读取。

答案

在微服务架构中,实现Session共享的关键是使用Redis存储Session信息。通过自定义SessionDAO覆盖Shiro默认的MemorySessionDAO,并使用Jedis客户端连接Redis,可以确保多个服务可以访问同一个用户的会话信息。

4.3 如果Redis挂了怎么办?

回答思路

  • 配置Redis的高可用性,如主从复制和哨兵模式。
  • 使用缓存降级策略,当Redis不可用时,可以暂时使用内存存储。
  • 确保系统的容错性和稳定性。

答案

如果Redis挂了,可以通过配置Redis的高可用性(如主从复制和哨兵模式)来解决。此外,可以使用缓存降级策略,当Redis不可用时,暂时使用内存存储。这些措施可以确保系统的容错性和稳定性。

五、总结

通过使用Shiro框架和Redis实现共享Session,成功解决了微服务架构中的权限控制问题。该方案实现了用户会话的共享和权限的统一管理,适用于需要精细权限控制的微服务项目。希望本文能帮助初学者更好地理解和实现微服务权限控制。

概括设计思想和为什么这么做 应用场景是什么样的

设计思想与核心原理


1. 设计思想
1.1 模块化与职责分离
  • 用户服务独立 :将用户认证(Authentication)与业务服务的权限校验(Authorization)解耦,用户服务仅负责登录验证,其他服务通过共享的common-auth模块进行权限管理。
    • 优势:避免权限逻辑重复,降低服务耦合。
    • 实现common-auth模块封装Shiro核心配置、自定义Realm和Redis Session管理,其他服务依赖此模块。
1.2 共享会话(Shared Session)
  • 核心问题:微服务环境下,用户在不同服务间的身份状态需保持一致。
  • 解决方案
    • Redis集中存储Session:所有服务从Redis读写Session,而非本地内存。
    • 自定义SessionDAO:覆盖Shiro默认实现,将Session序列化后存入Redis。
    • Cookie + Token双验证:Cookie携带Session ID,服务间调用通过Header传递Token(如JWT)。
1.3 统一权限管理
  • 动态权限加载:权限数据(角色、功能权限、数据权限)通过自定义Realm实时查询数据库或缓存,而非硬编码。
    • 缓存优化:使用Redis缓存权限数据,设置合理TTL,避免频繁查询数据库。
  • 注解式权限控制 :通过@RequiresRoles("admin")@RequiresPermissions("user:delete")等注解,在Controller层声明权限。
1.4 分布式数据同步
  • 服务间权限同步
    • RPC调用透传身份 :通过Dubbo的RpcContext隐式传递用户ID,服务端通过用户ID查询权限。
    • 事件驱动更新:权限变更时,发布消息到MQ(如Kafka),其他服务消费消息刷新本地缓存。

**2. 为什么这样做?
2.1 选择Shiro而非Spring Security
  • 轻量灵活:Shiro无强依赖Spring生态,适合非Spring Boot的微服务(如Dubbo+Zookeeper)。
  • 简化配置 :Shiro的ini配置或Java Config方式更易上手,适合快速迭代。
  • 分布式适配性:Shiro的SessionDAO和CacheManager可灵活替换为Redis实现,天然支持分布式场景。
2.2 使用Redis共享Session
  • 一致性:所有服务共享同一Session源,避免用户需重复登录。
  • 高可用:Redis集群+哨兵模式保障Session存储的可靠性。
  • 性能:Redis内存读写速度快,支持高并发场景。
2.3 分离用户服务与权限模块
  • 单一职责:用户服务专注于认证逻辑(如密码加密、多端登录),权限模块专注于鉴权。
  • 可扩展性 :新增业务服务(如订单服务)只需引入common-auth模块,无需重复开发权限逻辑。
2.4 动态权限加载
  • 实时性:用户权限变更后,下次请求即可生效(结合缓存过期策略)。
  • 灵活性:支持按需扩展权限维度(如部门、租户级数据权限)。

3. 应用场景
3.1 适用场景
  1. 中小型微服务架构
    • 项目规模较小,需快速落地权限控制。
    • 示例:企业内部管理系统(HR、OA)、电商后台(订单、商品管理)。
  1. 多租户SaaS应用
    • 不同租户拥有独立角色和权限集。
    • 通过数据权限(如WHERE tenant_id=?)隔离租户数据。
  1. 高并发分布式系统
    • 需保障Session一致性和服务无状态化。
    • 示例:在线教育平台(课程、视频服务分离)。
  1. 混合技术栈环境
    • 部分服务用Java+Shiro,其他服务用Go/Python,通过JWT实现跨语言鉴权。
3.2 不适用场景
  1. 超大规模权限系统
    • 权限层级复杂(如RBAC+ABAC混合模型),需更强大的框架(如Casbin)。
  1. 强安全合规场景
    • 需支持OAuth 2.0、SAML等协议,建议使用Spring Security + Keycloak。

4. 核心流程图
markdown 复制代码
用户登录 → 用户服务认证 → 生成Session存入Redis → 返回Session ID
          │
          ↓
访问业务服务 → 网关校验Session有效性 → 从Redis查询权限 → 鉴权通过 → 执行业务逻辑
                                │
                                鉴权失败 → 返回403

5. 方案优势总结
维度 传统方案 本方案
开发效率 Spring Security配置复杂 Shiro轻量,模块化设计,快速集成
性能 单点Session存储,扩展性差 Redis分布式Session,支持高并发
灵活性 权限逻辑硬编码,难动态调整 动态加载权限,支持实时更新
跨服务协作 依赖网关统一鉴权,权限逻辑集中 各服务独立鉴权,通过Redis共享状态

6. 演进方向
  • 无状态化改造:逐步迁移到JWT,减少对Redis的依赖。
  • 细粒度数据权限:引入规则引擎(如Drools),实现动态SQL拼接。
  • 审计与风控:记录用户操作日志,结合机器学习分析异常行为。

通过此方案,开发者可快速构建一个灵活、高可用的微服务权限系统,平衡开发效率与系统性能,适用于多数中小型分布式场景。

相关推荐
FirstMrRight几秒前
自动挡线程池OOM最佳实践
java·后端
程序员清风12 分钟前
Redis Pipeline 和 MGET,如果报错了,他们的异常机制是什么样的?
java·后端·面试
审计侠38 分钟前
Go语言-初学者日记(四):包管理
开发语言·后端·golang
Aska_Lv1 小时前
Linux---jstat命令的作用
后端
嘻嘻哈哈开森1 小时前
从零开始学习模型蒸馏
人工智能·后端
DataFunTalk2 小时前
大模型时代数据科学岗位的未来思考
前端·后端·算法
阮瑭雅2 小时前
Java语言的Web安全
开发语言·后端·golang
编程乐趣2 小时前
UnitOfWork:一个支持多数据库,工作单元模式、支持分布式事务以及支持 MySQL 多数据库/表分片的开源项目
后端
东方雴翾2 小时前
Dart语言的3D可视化
开发语言·后端·golang