简介
Shiro解决的核心问题
对常见的权限模块进行抽象,提炼通用的权限认证、授权信息等能力,认证信息可以选常见的存储组件存储,比如 redis 数据库。
核心构成
Shiro 的架构主要由三个核心组件构成:Subject、SecurityManager 和 Realm。
- Subject 代表了当前用户,绑定到当前线程
- SecurityManager 是安全管理器,是 Shiro 等控制器
- Realm 则是安全数据源,用于获取安全实体的认证信息和授权信息。
原理
如何抽象?
当你登录时,我们需要对身份进行校验,这个过程是认证
。
认证信息一般需要查询数据库,我们将这些动作封装在 Realm
,简单理解为专门执行 DAO 相关动作。
当认证通过后,我们需要判断当前用户能否进行访问当前接口,这个过程是 授权
。
我们可以提供一个实体信息,封装当前用户信息,程序可以直接操作和使用,这个是 Subject
。
如果我们选择 Session 存储,通过抽象出 SessionManager
统一管理 Session 信息。
Session 存储底层我们可以选择缓存,比如 Redis,抽象 CacheManager
进行统一管理。
...
最后,我们再抽象一个控制器 SecurityManager
,全局调度以上这些能力,如上,我们便完成了 Shiro 对权限能力的抽象提取。
Apache Shiro 核心抽象逻辑
哪些是抽象的逻辑?哪些是业务需要去实现的?
- SecurityManager:这是 Shiro 的核心,负责协调 Shiro 的其他组件。不需要实现它,只需要在配置 Shiro 时指定一个实例即可。
- Authenticator:负责 Subject 的认证。Shiro 提供了一些默认的实现,可以直接使用,也可以根据需要自定义。
- Authorizer:负责完成授权功能。Shiro 提供了一些默认的实现,可以直接使用,也可以根据需要自定义。
- SessionManager:如果使用了会话,SessionManager 管理会话的生命周期。Shiro 提供了一些默认的实现,可以直接使用,也可以根据需要自定义。
- CacheManager:Shiro 提供了缓存功能,可以直接使用,也可以根据需要自定义。
- Cryptography:Shiro 提供了一套简单易用的密码学工具,可以直接使用。
然后,有一些部分需要根据业务需求去实现的,例如:
- Realm:Realm 是一个特定的安全 DAO,它负责从数据源中获取安全实体,如用户的身份信息、角色、权限等。需要实现 Realm,以便 Shiro 可以从你的应用中获取这些信息。
- Subject:在 Shiro 中,Subject 是一个安全性的概念,它包含了当前用户的信息,如用户的身份信息、角色、权限等。需要根据业务需求来创建和管理 Subject。
- 用户的身份信息、角色、权限等:这些都是需要根据业务需求来定义和管理的。
- 登录和退出的逻辑:需要根据业务需求来实现用户的登录和退出逻辑
工作流程
一般来讲,Shiro 利用服务端存储 session 信息,登录时进行身份认证、授权,并将这些信息存储在服务端(比如 redis),然后返回 sessionId 给前端,前端每次请求都带上 sessionId。
请求到达时,Shiro 的 SessionManagementFilter
过滤器检测 sessionId,并加载对应的 session 信息,然后初始化 Subject 信息,最终将 Subject 绑定到当前线程中。
当前请求线程处理完毕后,清理掉当前线程的 Subject 信息。
0. 登录
主要步骤:
- 收集用户身份/凭证:通常是在用户登录的时候收集。如用户名/密码,或者在实现单点登录(single sign-on)时,可能是一个交换的 token。身份可以是任何东西,但凭证通常是敏感的(如密码),所以最好在可能的情况下尽快获取并销毁凭证。
- 构建 AuthenticationToken:Shiro 通过 AuthenticationToken 接口获取用户的身份/凭证,该接口有一个实现类 UsernamePasswordToken,可以使用它来获取用户名/密码。
- 获取 Subject:在 Shiro 中,Subject 是一个安全性的概念,包含了当前用户的所有安全操作。可以通过 SecurityUtils.getSubject() 来获取。
- 登录:通过调用 Subject 的 login 方法来登录。
示例:
java
// 收集用户身份/凭证,通常是在用户登录的时候收集
String username = "admin";
String password = "123456";
// 构建 AuthenticationToken
AuthenticationToken token = new UsernamePasswordToken(username, password);
// 获取 Subject
Subject currentUser = SecurityUtils.getSubject();
// 登录
try {
currentUser.login(token);
// 登录成功
} catch (AuthenticationException ae) {
// 登录失败
}
1. 请求到达
当一个请求到达应用时,它首先会被 Shiro 的过滤器链处理。
Shiro提供了一系列的过滤器,如身份验证过滤器、授权过滤器等,可以根据需要配置这些过滤器。
上一步我们实现了登录,并将登录信息存储到 Session 中,当请求到达时,服务端要尝试获取 session 信息,并创建 Subject 实体,提供给整个请求生命周期使用,负责处理整个动作的是 SessionManagementFilter
:
- 会话管理器会从请求中获取 sessionId,然后使用这个 sessionId 来查找对应的会话。如果找到了对应的会话,会话管理器就会返回这个会话,否则,它会创建一个新的会话。
- 当 SessionManagementFilter 获取到一个会话后,它会将这个会话存储在当前线程的 Subject 实例中。这样,就可以在当前线程的任何位置通过 SecurityUtils.getSubject() 获取到这个 Subject 实例,并从中获取到会话信息。
2. 身份验证
如果请求需要身份验证,Shiro 会使用配置的 SecurityManager 和 Realm 来完成身份验证。
具体来说,Shiro 会创建一个 AuthenticationToken,包含用户提供的身份(如用户名)和凭证(如密码),然后通过 SecurityManager 将这个 AuthenticationToken 传递给 Realm
Realm 会使用这个 AuthenticationToken 来从数据源中获取相应的身份信息和凭证,然后比较它们是否匹配。
3. 授权
如果请求需要授权,Shiro 会使用配置的 SecurityManager 和 Realm 来完成授权。
具体来说,Shiro 会创建一个 AuthorizationInfo,包含用户的角色和权限,然后通过SecurityManager 将这个 AuthorizationInfo 传递给Realm。
Realm 会使用这个 AuthorizationInfo 来从数据源中获取相应的角色和权限,然后比较它们是否匹配。
4. 执行操作
如果身份验证和授权都成功,Shiro 会继续处理请求,执行相应的操作。
5. 会话管理
在整个过程中,Shiro 还会管理用户的会话。
Shiro 提供了一套会话管理 API,可以使用它来获取和操作用户的会话。
6. 退出
当用户完成操作并退出应用时,Shiro 会清理用户的会话和身份信息。
多个 realm?
在 Apache Shiro中,可以配置多个 Realm,以便从不同的数据源获取用户的身份信息、角色、权限等。
例如,可能有一个 Realm 用于从 MySQL 数据库中获取用户信息,另一个 Realm 用于从 第三方服务中获取用户信息。
当配置了多个 Realm 时,Shiro 的 SecurityManager 会按照它们在配置中的顺序来调用它们。当一个 Realm 无法处理一个身份验证或授权请求时,SecurityManager 会将请求传递给下一个 Realm。
身份验证
对于身份验证(Authentication),Shiro 会遍历所有配置的 Realm,只要有一个 Realm 能够验证成功,那么整个身份验证就会成功。
如果所有 Realm 都无法验证成功,那么身份验证就会失败。
授权
对于授权(Authorization),Shiro 会收集所有 Realm 的授权信息,然后进行聚合。
例如,如果有多个 Realm,Shiro 会将所有 Realm 的授权信息(角色、权限等)进行合并,然后基于这些合并后的授权信息来进行授权决策。
所以,对于身份验证,任意一个 Realm 能处理就行;对于授权,Shiro 会考虑所有 Realm 的授权信息
实践
最小可用的 shiro 实战:
- 添加Shiro依赖:在你的项目中添加 Shiro 的依赖。如果使用的 Maven,可以在 pom.xml 文件中添加依赖:
xml
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version>
</dependency>
- 创建Realm:创建一个自定义的 Realm,用于从数据源中获取用户的身份信息、角色、权限等。以下是一个简单的 Realm 实现:
java
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 在这里完成授权信息的获取
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 在这里完成认证信息的获取
return null;
}
}
- 配置SecurityManager:创建一个 DefaultSecurityManager 实例,并为其设置你创建的 Realm:
java
MyRealm myRealm = new MyRealm();
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(myRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
- 登录:创建一个 Subject 实例,并使用它来完成登录:
ini
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
subject.login(token);
- 授权:一旦 Subject 被认证(登录成功),就可以使用它来完成授权,即确定 Subject 是否有权限执行某个操作:
less
if (subject.hasRole("admin")) {
// 如果Subject有"admin"角色,执行相应的操作
}
- 退出:当 Subject 完成操作并退出应用时,可以使用它来完成退出:
ini
subject.logout();