Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
本教程只介绍基本的 Shiro 使用,不会过多分析源码等,重在使用。
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
接下来我们分别从外部和内部来看看 Shiro 的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的 API,且 API 契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。
首先,我们从外部来看 Shiro 吧,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图:
可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;其每个 API 的含义:
Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
接下来我们来从 Shiro 内部来看下 Shiro 的架构,如下图所示:
简单的demo:
我们使用maven构建,其中需要将shiro的依赖注入
org.apache.shiro
shiro-core
1.5.3
添加resources文件夹 用来存放配置文件
shiro的配置文件为.ini 结尾的文件,类似于txt
ini 中可以写比较复杂的数据格式 不支持 properties yml
创建shiro.ini文件 书写系统中相关权限数据
1、首先准备一些用户身份 / 凭据(shiro.ini)
[users]
zhangsan=123
lisi=456
wangwu=789
此处使用 ini 配置文件,通过 [users] 指定了两个主体:zhang/123、wang/123。
2、测试用例
package com.wenhao;
import com.sun.org.apache.xerces.internal.util.SecurityManager;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
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;
public class TestAuthenticator {
public static void main(String[] args) {
//创建安全管理器对象
DefaultSecurityManager SecurityManager = new DefaultSecurityManager();
//给安全管理器设置realm
SecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//securityUtils 给全局安全工具类设置安全管理器 提供了认证,退出等相关方法
SecurityUtils.setSecurityManager(SecurityManager);
//关键对象 subject 主题
Subject subject = SecurityUtils.getSubject();
//创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123");
try {
//用户认证
System.out.println("认证状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
}
}
首先通过 new IniSecurityManagerFactory
并指定一个 ini
配置文件来创建一个 SecurityManager
工厂;
接着获取 SecurityManager
并绑定到 SecurityUtils
,这是一个全局设置,设置一次即可;
通过 SecurityUtils
得到 Subject
,其会自动绑定到当前线程;如果在 web 环境在请求结束时需要解除绑定;然后获取身份验证的 Token
,如用户名 / 密码;
调用 subject.login
方法进行登录,其会自动委托给 SecurityManager.login
方法进行登录;
如果身份验证失败请捕获 AuthenticationException
或其子类,常见的如: DisabledAccountException
(禁用的帐号)、LockedAccountException
(锁定的帐号)、UnknownAccountException
(错误的帐号)、ExcessiveAttemptsException
(登录失败次数过多)、IncorrectCredentialsException
(错误的凭证)、ExpiredCredentialsException
(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如 “用户名 / 密码错误” 而不是 “用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
subject.logout
退出,其会自动委托给 SecurityManager.logout
方法退出。这里我们学习两个单词:
UnknownAccountException 未知账户异常
IncorrectCredentialsException 无效的品正信息异常
从如上代码可总结出身份验证的步骤:
收集用户身份 / 凭证,即如用户名 / 密码;
调用 Subject.login
进行登录,如果失败将得到相应的 AuthenticationException
异常,根据异常提示用户错误信息;否则登录成功;
Subject.logout
进行退出操作。如上测试的几个问题:
用户名 / 密码硬编码在 ini
配置文件,以后需要改成如数据库存储,且密码需要加密存储;
Token
可能不仅仅是用户名 / 密码,也可能还有其他的,如登录时允许用户名 / 邮箱 / 手机号同时登录。流程如下:
Subject.login(token)
进行登录,其会自动委托给 Security Manager
,调用之前必须通过 SecurityUtils.setSecurityManager()
设置;SecurityManager
负责真正的身份验证逻辑;它会委托给 Authenticator
进行身份验证;Authenticator
才是真正的身份验证者,Shiro API
中核心的身份认证入口点,此处可以自定义插入自己的实现;Authenticator
可能会委托给相应的 AuthenticationStrategy
进行多 Realm
身份验证,默认 ModularRealmAuthenticator
会调用 AuthenticationStrategy
进行多 Realm
身份验证;Authenticator
会把相应的 token
传入 Realm
,从 Realm
获取身份验证信息,如果没有返回 / 抛出异常表示身份验证成功了。此处可以配置多个 Realm
,将按照相应的顺序及策略进行访问。