微服务权限控制方案详解

微服务权限控制方案详解

一、微服务权限设计

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拼接。
  • 审计与风控:记录用户操作日志,结合机器学习分析异常行为。

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

相关推荐
Victor3569 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易9 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧9 小时前
Range循环和切片
前端·后端·学习·golang
WizLC9 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor3569 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法9 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长10 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
yaoh.wang10 小时前
力扣(LeetCode) 1: 两数之和 - 解法思路
python·程序人生·算法·leetcode·面试·跳槽·哈希算法
Python编程学习圈11 小时前
Asciinema - 终端日志记录神器,开发者的福音
后端
bing.shao11 小时前
Golang 高并发秒杀系统踩坑
开发语言·后端·golang