Shrio简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
shiro主要功能
-
Subject: 主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;
所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;
可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者; -
SecurityManager: 安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;
可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,
可以把它看成SpringMVC的 DispatcherServlet 前端控制器; -
Realm: 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,
那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;
也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;
可以把 Realm 看成 DataSource,即安全数据源。 -
简单的一个 Shiro 应用:
1、应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
2、我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
Shiro架构
- Shrio不提供维护用户/权限,而是通过Realm让开发人员自己住入
Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;
SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;
所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;
其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;
可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;
注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;
而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;
所以Shiro 抽象了一个自己的 Session 来管理主体与应用之间交互的数据;
这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;
这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,
比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;
比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;
另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密的。
创建Springboot工程,导入shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version>
</dependency>
<!-- 这里有用到日志打印,所以引入 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
创建shiro.ini文件
- 在resources下创建shiro.ini
[users]
shiro=123456
boot=456789
测试代码
package com.shiro.test;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
@Slf4j
public class ShiroTest {
public static void main(String[] args) {
// 1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.给安全管理器设置realm(范围)
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3.SecurityUtils给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.关键对象subject主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
// 假设username、password是从数据库获取
UsernamePasswordToken token = new UsernamePasswordToken("shiro", "123456");
try {
// 认证前
log.info("认证前状态:" + subject.isAuthenticated());
// 执行用户认证
subject.login(token);
// 认证后
log.info("认证后状态:" + subject.isAuthenticated());
} catch (AuthenticationException e) {
e.printStackTrace();
log.error("认证失败,用户名或密码不存在");
}
}
}
如果用户名密码错误
UsernamePasswordToken token = new UsernamePasswordToken("shiro", "111111");
提示异常信息
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2020.1\lib\idea_rt.jar=14731:D:\Program Files\JetBrains\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;E:\SpringBoot_Code\shiro\target\classes;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-core\1.7.1\shiro-core-1.7.1.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-lang\1.7.1\shiro-lang-1.7.1.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-cache\1.7.1\shiro-cache-1.7.1.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-crypto-hash\1.7.1\shiro-crypto-hash-1.7.1.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-crypto-core\1.7.1\shiro-crypto-core-1.7.1.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-crypto-cipher\1.7.1\shiro-crypto-cipher-1.7.1.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-config-core\1.7.1\shiro-config-core-1.7.1.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-config-ogdl\1.7.1\shiro-config-ogdl-1.7.1.jar;D:\Users\HL\.m2\repository\commons-beanutils\commons-beanutils\1.9.4\commons-beanutils-1.9.4.jar;D:\Users\HL\.m2\repository\commons-collections\commons-collections\3.2.2\commons-collections-3.2.2.jar;D:\Users\HL\.m2\repository\org\apache\shiro\shiro-event\1.7.1\shiro-event-1.7.1.jar;D:\Users\HL\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\Users\HL\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;D:\Users\HL\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;D:\Users\HL\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;D:\Users\HL\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;D:\Users\HL\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;D:\Users\HL\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;D:\Users\HL\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;D:\Users\HL\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;D:\Users\HL\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\Users\HL\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\Users\HL\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;D:\Users\HL\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;D:\Users\HL\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\Users\HL\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\Users\HL\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;D:\Users\HL\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;D:\Users\HL\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;D:\Users\HL\.m2\repository\org\springframework\boot\spring-boot-devtools\2.4.3\spring-boot-devtools-2.4.3.jar;D:\Users\HL\.m2\repository\org\projectlombok\lombok\1.18.18\lombok-1.18.18.jar" com.shiro.test.ShiroTest
15:52:36.390 [main] DEBUG org.apache.shiro.io.ResourceUtils - Opening resource from class path [shiro.ini]
15:52:36.398 [main] DEBUG org.apache.shiro.config.Ini - Parsing [users]
15:52:36.400 [main] DEBUG org.apache.shiro.realm.text.IniRealm - Discovered the [users] section. Processing...
15:52:36.407 [main] INFO com.shiro.test.ShiroTest - 认证前状态:false
15:52:36.407 [main] DEBUG org.apache.shiro.realm.AuthenticatingRealm - Looked up AuthenticationInfo [shiro] from doGetAuthenticationInfo
15:52:36.407 [main] DEBUG org.apache.shiro.realm.AuthenticatingRealm - AuthenticationInfo caching is disabled for info [shiro]. Submitted token: [org.apache.shiro.authc.UsernamePasswordToken - shiro, rememberMe=false].
15:52:36.407 [main] DEBUG org.apache.shiro.authc.credential.SimpleCredentialsMatcher - Performing credentials equality check for tokenCredentials of type [[C and accountCredentials of type [java.lang.String]
15:52:36.408 [main] DEBUG org.apache.shiro.authc.credential.SimpleCredentialsMatcher - Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison
15:52:36.409 [main] ERROR com.shiro.test.ShiroTest - 认证失败,用户名或密码不存在
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - shiro, rememberMe=false] did not match the expected credentials.
at org.apache.shiro.realm.AuthenticatingRealm.assertCredentialsMatch(AuthenticatingRealm.java:603)
at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:581)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273)
at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275)
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260)
at com.shiro.test.ShiroTest.main(ShiroTest.java:31)
Process finished with exit code 0
常用异常类型
- 错误的帐号:UnknownAccountException
- 错误的凭证(密码):IncorrectCredentialsException
- 禁用的帐号:DisabledAccountException
- 锁定的帐号:LockedAccountException
- 登录失败次数过多:ExcessiveAttemptsException
- 过期的凭证:ExpiredCredentialsException