Shiro可以用于普通Java工程或JavaWeb工程,针对不同的环境,框架内部会进行一些处理,我们不用关心。这篇笔记我们以一个普通Java工程为例,介绍如何用Shiro实现认证和鉴权。
为了熟悉Shiro中的一些组件和API,我们最好先在普通Java工程中搭建一个Shiro工程用来测试学习。
新建一个普通Maven工程,添加依赖引入Shiro框架的核心功能。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
警告:Shiro作为一个认证授权框架,一直是信息安全领域漏洞挖掘的关注重点,随着时间的推移上面例子中所使用的Shiro版本可能已经过时,在实际开发中我们务必选择一个没有安全漏洞的较新版本。
此外Shiro还依赖于日志框架,我们引入SLF4J和Logback日志框架。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.26</version>
</dependency>
这里注意Shiro
默认依赖于commons-logging
这个过时的日志框架(这是Shiro的一个糟糕设计),为了统一使用Logback
,我们需要jcl-over-slf4j
桥接器来将Shiro的日志用Logback输出。
我们知道RBAC模型中,说白了是用户<->角色<->权限三者的多对多关系,在工程实践中,为了使授权粒度更细,还可能会有用户直接关联权限的设计,这些和权限相关的内容一般存储在数据库中,工程启动时可能还会放入缓存集群,以提高系统整体性能。
这里我们只是简单体验Shiro的用法因此就不搞那么复杂了,Shiro支持通过一个shiro.ini
文件编写用户、角色、权限用于测试,当然,这种情况比较少用于线上环境。
[users]
tom = 123abc, guest, admin
jerry = abc123, guest
lilei = lilei666, guest, doc1_editor
lucy = qqqwwweee, guest, doc1_editor, doc1_manager
[roles]
admin = *
guest = doc:query
doc1_editor = doc:query:doc1, doc:create:doc1, doc:update:doc1
doc1_manager = doc:query:doc1, doc:update:doc1, doc:delete:doc1
我们这里假设有一个大型CMS系统,分为文档doc
,视频video
等多个大板块,文档板块分为doc1
、doc2
等若干区划,针对每个分区都有对应的工作人员,包括编辑editor
,经理manager
,除此之外,还有超级管理员admin
,以及游客guest
。
[users]
中定义的是该用户的用户名,登录凭证,所属角色。例如,lilei
的密码是lilei666
,所属角色是guest
访客,和doc1_editor
文档1编辑。
[roles]
中定义的是角色和权限,例如:doc1_manager
表示doc1
分区的经理,他能对doc
板块下,doc1
区内容有查询、更新、删除权限,但是不能擅自创建,doc1_editor
表示doc1
分区的编辑,他能查询、创建、更新内容,但不能擅自删除。
注意权限的写法,例如doc:query:doc1
,表示针对doc
下的doc1
,有查询权限,即:<访问主体>:<访问动作>:<访问主体的实例>
。*
代表对该字段的所有取值都有权限,位于授权字符串后面的*
可以省略。
package com.gacfox.demo.demoshiro;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
* @author gacfox
*/
@Slf4j
public class Main {
public static void main(String[] args) {
// 初始化SecurityUtils
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 获取当前访问客体对象
Subject subject = SecurityUtils.getSubject();
// 登录实现
UsernamePasswordToken token = new UsernamePasswordToken("lucy", "qqqwwweee");
token.setRememberMe(true);
try {
subject.login(token);
} catch (UnknownAccountException e) {
log.debug("没有该账号");
} catch (IncorrectCredentialsException e) {
log.debug("错误的登录凭证");
} catch (LockedAccountException e) {
log.debug("账号已被锁");
} catch (AuthenticationException e) {
log.debug("具体原因不明,反正就是登录出错了");
}
// 登录成功,鉴权实现
boolean[] permsCheck = subject.isPermitted(
"doc:query:doc1",
"doc:create:doc1",
"doc:update:doc1",
"doc:delete:doc1",
"doc:query:doc2",
"doc:create:doc2",
"doc:update:doc2",
"doc:delete:doc2");
log.debug("能否查询doc1 " + permsCheck[0]);
log.debug("能否创建doc1 " + permsCheck[1]);
log.debug("能否更新doc1 " + permsCheck[2]);
log.debug("能否删除doc1 " + permsCheck[3]);
log.debug("能否查询doc2 " + permsCheck[4]);
log.debug("能否创建doc2 " + permsCheck[5]);
log.debug("能否更新doc2 " + permsCheck[6]);
log.debug("能否删除doc2 " + permsCheck[7]);
}
}
可以看到,上面代码其实就是实现了两个功能:登录和鉴权。
登录过程中,我们调用subject.login()
,它会抛出各种运行时异常(这又是Shiro的糟糕设计,Java开发中另一个拒绝用受检查异常的极端,我认为AuthenticationException
至少应该是受检查异常),我们可以捕获这些运行时异常,进行各种错误处理。
代码中编写的就是最常用的几种异常:
UnknownAccountException
:无账号IncorrectCredentialsException
:密码错误LockedAccountException
:账号已锁定AuthenticationException
:Shiro中各种异常的基类鉴权就比较简单了,调用subject.isPermitted()
,参数是我们要检查的权限,返回值表示是否有这些权限。