好的,我们先从逻辑层面理解Shiro在JVM中是如何工作的,先抛开具体代码。
一、核心逻辑架构
想象Shiro在JVM中创建了一个 "安全控制中心",这个中心有四大部门:
部门1:门卫系统(Security Manager - 安全管理器)
- 职位:总指挥官,协调所有安全工作
- 工作流程 :
- 应用程序启动时,创建唯一的SecurityManager实例(单例模式)
- 它手下管理着:Realm(数据库查询员)、Session Manager(会话管理员)、Cache Manager(缓存员)
- 所有安全请求都先到这里,它再分派给具体部门处理
部门2:身份验证科(Authentication - 登录认证)
-
工作原理 :
用户登录 → 创建登录令牌(Token) → SecurityManager接收 → 交给Realm查询员 → 查询数据库验证 → 返回认证结果 -
关键"物件" :
- Subject(用户身份证):代表当前操作用户,每个请求线程一个
- Token(登录申请单):包含用户名密码等凭证
- Realm(数据库查询员):负责去数据库查真实用户信息
部门3:权限检查科(Authorization - 授权检查)
-
工作原理 :
用户访问资源 → SecurityManager检查 → Realm查询员查权限 → 对比所需权限 → 通过/拒绝 -
权限存储方式 :
- 角色:如"管理员"、"普通用户"(粗粒度)
- 权限:如"user:create"、"order:delete"(细粒度)
部门4:会话管理科(Session Management)
- 独特之处 :Shiro有自己的会话系统,独立于HttpSession
- 工作方式 :
- 用户登录后创建Shiro Session
- 每个Session有唯一ID,存储在Cookie中
- 下次请求通过Cookie找到Session,恢复用户状态
二、JVM中创建的"人物"(类实例)
1. SecurityUtils(安全工具人)
- 作用:提供快速访问当前用户的方法
- 工作方式:通过ThreadLocal绑定Subject到当前线程
- 类比:就像公司前台,随时告诉你"当前谁在办事"
2. Subject(当前用户代表)
- 创建时机:每个请求开始时(通过Filter创建)
- 存储位置:ThreadLocal(线程局部变量)
- 生命周期:一个请求周期,随请求结束而清理
- 实际身份:只是用户的一个"代理",不是真实用户对象
3. Realm(数据源连接员)
- 数量:可以配置多个(如:DB Realm、LDAP Realm)
- 工作流程 :
认证请求
SecurityManager
选择Realm
DB Realm
LDAP Realm
查数据库
查LDAP
返回用户信息
4. Filter(过滤器链)
-
位置:在Servlet最外层,像安检通道
-
工作流程 :
请求到来 → 通过Filter链 → 每个Filter检查权限 → 放行或拦截 → 到达Controller -
Filter类型举例 :
anon通道:免检通道(登录页、静态资源)authc通道:必须出示身份证(需要登录)roles通道:检查工作证角色perms通道:检查具体权限卡
三、数据流转逻辑
登录过程:
1. 用户提交登录表单
2. 创建UsernamePasswordToken(登录申请表)
3. subject.login(token)提交申请
4. SecurityManager收到申请
5. 派一个或多个Realm去数据库核实
6. Realm返回:此人存在且密码正确
7. SecurityManager:创建Session,记录登录状态
8. 返回登录成功,后续请求携带Session ID
权限检查过程:
用户访问/admin/user/list页面:
1. Filter拦截到/admin/**路径需要"admin"角色
2. 从当前Subject获取用户身份
3. 问SecurityManager:这人有没有admin角色?
4. SecurityManager问Realm:查一下这人的角色列表
5. Realm返回:他有["user", "editor"]角色,没有"admin"
6. SecurityManager:拒绝访问,跳转到未授权页面
四、会话管理的特别设计
Shiro Session vs HttpSession:
传统方式:
浏览器 → HTTP请求 → Tomcat容器 → 创建HttpSession → 存储用户状态
Shiro方式:
浏览器 → HTTP请求 → Shiro Filter → 创建Shiro Session →
→ 可选择:1.存储在内存 2.存储在Redis 3.存储在数据库
→ Session独立于Servlet容器
优势:即使更换Web容器(Tomcat→Jetty),会话依然有效
五、加密处理逻辑
密码存储验证流程:
用户注册时:
1. 用户输入密码"123456"
2. Shiro生成随机盐值"salt123"
3. 使用算法:md5(密码 + 盐值 + 迭代2次)
4. 存储到数据库:加密后的密码 + 盐值
用户登录时:
1. 用户输入密码"123456"
2. 从数据库取出:加密密码 + 盐值
3. Shiro用同样算法计算:md5(输入密码 + 盐值 + 迭代2次)
4. 比较两个加密结果是否一致
六、内部状态管理图
有Subject
无Subject
是
否/新Session
是
否
是
否
浏览器请求
Shiro Filter
检查ThreadLocal
获取已有Subject
创建新Subject
检查Session ID
生成新Session
Session有效?
恢复用户状态
创建新Session状态
执行权限检查
用户未登录状态
有权限?
执行业务逻辑
返回403未授权
需要登录?
重定向到登录页
响应返回
七、关键设计思想
-
单一职责:每个类只做一件事
- SecurityManager只做协调
- Realm只做数据查询
- Subject只代表用户
-
无状态设计:服务端不存储状态,状态在Session中
-
插件化架构:可以替换任何组件
- 换数据源?换Realm实现
- 换Session存储?换SessionDAO
- 换缓存?换CacheManager
-
线程隔离:每个请求独立Subject,互不干扰
这样的设计让Shiro既灵活又可靠,就像一个设计良好的安保公司:分工明确、流程清晰、可扩展性强。