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

1.2 微服务权限设计的挑战
- 分布式环境:微服务环境下,多个服务分布在不同的服务器上,每个服务都需要独立进行认证和授权。
- 服务间调用:微服务之间需要通过API相互调用,这时如何确保跨服务的权限认证是一个挑战。
- 功能权限和数据权限的统一管理:功能权限主要控制用户能访问哪些功能模块,而数据权限则控制用户能访问哪些具体数据记录。
1.3 设计思路
- 用户服务与Shiro模块分离:用户服务负责用户认证,其他服务共享一个Shiro模块进行权限校验。
- Redis共享Session:通过Redis存储用户的会话信息,确保多个服务可以访问同一个用户的会话。
- 服务间的权限数据同步:通过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?
- 自定义SessionDAO:覆盖Shiro默认的MemorySessionDAO,使用Redis存储Session。
- 自定义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<>();
}
}
- 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) {
// 获取键值对
}
}
- 自定义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();
}
}
三、测试结果
- 未登录访问接口:跳转到Shiro默认的登录页面。
- 登录后访问接口:成功访问受保护的接口。
- 权限校验:未授权用户访问受保护接口时,返回未授权提示。
四、面试回答思路和答案
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),其他服务消费消息刷新本地缓存。
- RPC调用透传身份 :通过Dubbo的
**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 适用场景
- 中小型微服务架构:
-
- 项目规模较小,需快速落地权限控制。
- 示例:企业内部管理系统(HR、OA)、电商后台(订单、商品管理)。
- 多租户SaaS应用:
-
- 不同租户拥有独立角色和权限集。
- 通过数据权限(如
WHERE tenant_id=?
)隔离租户数据。
- 高并发分布式系统:
-
- 需保障Session一致性和服务无状态化。
- 示例:在线教育平台(课程、视频服务分离)。
- 混合技术栈环境:
-
- 部分服务用Java+Shiro,其他服务用Go/Python,通过JWT实现跨语言鉴权。
3.2 不适用场景
- 超大规模权限系统:
-
- 权限层级复杂(如RBAC+ABAC混合模型),需更强大的框架(如Casbin)。
- 强安全合规场景:
-
- 需支持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拼接。
- 审计与风控:记录用户操作日志,结合机器学习分析异常行为。
通过此方案,开发者可快速构建一个灵活、高可用的微服务权限系统,平衡开发效率与系统性能,适用于多数中小型分布式场景。