Apache Shiro 示例
你的第一个Apache Shiro 应用
如果你是第一次访问Apache Shiro,本简易教程将为你展示如何使用Apache
Shiro设置一个基本且简单的安全应用.我们将通过讨论Shiro的核心概念的方式来帮助你熟悉Shiro的设计和API.
如果你不想在学习的时候编辑代码,你可以获得一个基本相同的样例程序并在学习时参阅,下面是示例程序的不同下载方式:
- 在Apache Shiro的git仓库中:
https://github.com/apache/shiro/tree/master/samples/quickstart - 在Apache Shiro的源分发地址的
samples/quickstart
目录下.可点击 下载
进入源分发页面
2
设置
在本简单样例中,我们将会创建一个非常简单的命令行程序,你可以非常快的运行和退出,以此来感受Shiro的API.
任何应用
Apache Shiro从第一天的设计开始就支持任意应用,从最简单的命令行程序到大型集群web应用,即使如此我们依旧通过一个简单的app来作为开始的教程,以此来让你了解一件事,你的程序无论被创建或被部署到什么地方,这些应用都采用相同的模式来使用Shiro提高安全性.
本教程需要Java1.6或更加高级的版本,同时我们也会使用Apache Maven来作为我们的项目构建工具.但这对应使用Apache Shiro来说并不是必须的,你可以获取Apache shiro的Jar包文件并可以用任意方式集成到你的程序中.比如还有Apache的 Ant和Ivy.
对于本次演示,请保证你的maven版本大于等于2.2.1,你可以通过敲击命令
mvn --version
来看到如下的版本提示信息.
测试maven安装
hazlewood:~/shiro-tutorial$ mvn --version
Apache Maven 2.2.1 (r801777; 2009-08-06 12:16:01-0700)
Java version: 1.6.0_24
Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
Default locale: en_US, platform encoding: MacRoman
OS name: "mac os x" version: "10.6.7" arch: "x86_64" Family: "mac"
4
现在,创建一个目录,在你的文件系统中,比如名字叫做
shiro-tutorial
并保存下面的Maven的pom.xml
文件到你刚创建的文件夹内.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.shiro.tutorials</groupId>
<artifactId>shiro-tutorial</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>First Apache Shiro Application</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<!-- 此插件只在测试运行我们的小应用,它在大多数需要Shiro的应用中是永不到的 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<classpathScope>test</classpathScope>
<mainClass>Tutorial</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Shiro使用SLF4J来记录日志,我们将在此应用中使用简单绑定,具体详见: http://www.slf4j.org -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
5
我们将会运行一个简单的命令行程序,因此我们需要创建一个带有
public static void main(String[] args)
主方法的Java类.先找到你的pom.xml
文件,在同一目录下创建一个src/main/java
子目录,在子目录下创建一个Tutorial.java
文件,文件内容如下:src/main/java/Tutorial.java
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.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Tutorial {
private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
public static void main(String[] args) {
log.info("My First Apache Shiro Application");
System.exit(0);
}
}
6
先不要担心引入的声明,我们马上就会用到他们.但现在我们可以通过命令行来运行此程序,(说明:Linux下shell,在windows下是cmd),文件中所有的代码程序只会做一件事,那就是输出文本"My First Apache Shiro Application"并退出.
7
运行测试
为了尝试运行我们的示例程序,需要在你的示例项目的根目录下(比如 shiro-tutorial
目录下)执行下面的命令:
mvn compile exec:java
随后你会看到我们的小示例应用的运行和结束.你应该会看到一些类似下面的内容(红色加粗代表我们的输出):
运行程序
lhazlewood:~/projects/shiro-tutorial$ mvn compile exec:java
... 一系列的 Maven 输出 ...
1 [Tutorial.main()] INFO Tutorial - My First Apache Shiro Application
lhazlewood:~/projects/shiro-tutorial\$
我们确定此程序运行可以成功,现在让我们开始使用Apache Shiro.在我们继续此示例程序的同时,当我们添加了代码后,你可以在在任意时刻运行
mvn compile exec:java
命令来查看不同的运行结果.
启用Shiro
在一个应用中启动Shiro,你首先需要理解的是,几乎在Shiro中所有的内容都依赖于被我们称为SecurityManager的核心组件.对于熟悉Java安全的人,你们要注意这个组件和
java.lang.SecurityManager
不是一个东西.当然,我们也会在体系结构那一章回顾Shiro的设计细节,早点了解是有好处的,Shiro的
Security Manager
是一个应用中Shiro环境的核心,而且一个SecurityManager必须放入一个应用,因此,我们在示例应用中要做的第一件事就是设置SecurityManager
的实例.9
配置
在我们可以直接创建一个SecurityManager类的时候,Shiro的SecurityManager实现类拥有足够多的配置选项和内部组件以至于会让你在使用Java代码编程的时候感到些许痛苦.不要担心,只要将SecurityManager的配置代码换成灵活的,基于文本的配置,此过程将变得异常简单。
为了实现那个目标,Shiro提供了一种通用的解决方法,一种基于 INI 格式的文档文件的配置方案。近些时间,人们开始不喜欢笨重的XML配置文件,反观INI文件,读起来容易,用着也简单,而且也只需要很少的依赖。一会儿你就能看到一个简单的易于理解的对象引导,通过INI文件可以被用来高效的配置简单的对象,就像使用SecurityManager一样。
10
多种配置选项
Shiro的实现类和所有支持的组件全部兼容JavaBean,这允许Shiro可以兼容几乎任何类型的配置文件比如XML(Spring,JBoss,Guice,等等),YAML,JSON,Groovy Builder markup,还有其他,INI文件只是Shiro的常用配置文件,且可以保证在任意环境下都可以使用.
shiro.ini
于是我们可以使用INI文件来为这个小程序配置
SecurityManager
,首先在与pom.xml
所在目录相同的目录下创建目录src/main/resources
,然后在新文件夹下创建shiro.ini文件,文件的内容如下:src/main/resources/shiro.ini
# =============================================================================
# 示例INI配置
#
# 用户名和密码基于经典的 Mel Brooks' film "Spaceballs" :)
# =============================================================================
# -----------------------------------------------------------------------------
# 用户和他们的(可选的)角色
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# 角色和他们关联的权限
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5
12
正如你所看到的,这个配置文件基本上设置了一个很小的静态的用户账户集合,已经足够我们的第一个小程序来使用了.在接下来的章节中,你将会看到我们如何使用更加灵活的数据源,比如关系数据库,LDAP和ActiveDirectory等.
13
引用配置文件
现在我们有了定义好的INI文件,我们可以在我们的示例程序类中创建SecurityManager实例了.现在在main
方法中进行如下更新:
public static void main(String[] args) {
log.info("My First Apache Shiro Application");
//1.
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.
SecurityManager securityManager = factory.getInstance();
//3.
SecurityUtils.setSecurityManager(securityManager);
System.exit(0);
}
14
现在好了,Shiro可以使用了,仅仅添加了3行代码,是不是很简单?
现在运行
mvn compile exec:java
然后查看一切运行正常(因为Shiro的默认为调试日志输出或更低级别的日志模式,你将不会看到任何Shiro日志信息,假如在运行后没有任何错误,就代表一切正常).下面讲解上述添加内容的功能介绍:
- 我们使用Shiro的
IniSecurityManagerFactory
实现来获取我们的shiro.ini文件(位于classpath的根目录下).此实现反映了Shiro对于工厂方法设计模式的支持.classpath:
前缀是一个资源标识符表名shiro从何处加载ini文件内容(其他前缀,比如url:
和file:
也同样支持) - 调用
factory.getInstance()
方法来解析INI文件并返回一个根据文件配置完成的SecurityManager
实例. - 在此简单案例中,我们设置
SecurityManager
为一个静态单例方法,通过JVM可自动获取.要注意到如果有多个Shiro配置在一个应用中此时就无法获取到实例.在此简单案例中,他是可以获取的,但更加复杂的应用环境通常将替换SecurityManager
在特定应用内存(比如网页应用的ServletContext
或者Spring,Guice或者JBoss DI容器实例).
15
使用Shiro
现在我们的SecurityManager配置完成并可以使用了.我们可以开始做一些我们真正关心的执行安全选项.
当要保护我们的应用时,获取最需要问的问题就是谁是当前用户?或者当前用户被允许做X操作了吗?这些问题经常在我们写代码或者设计用户接口时被提到,应用程序通常是基于用户信息构建的,你希望基于每个用户来表示(和保护)调用的功能.因此,我们考虑应用程序安全性的最自然的方法是基于当前用户.Shiro的API使用Subject
从根本上代表了"当前用户"的概念.
在几乎所有环境中,你可以通过如下调用获取当前正在访问的用户变量:
Subject currentUser = SecurityUtils.getSubject();
16
使用
SecurityUtils.getSubject()
方法,我们可以得到当前访问的Subject
对象.Subject是一个安全术语,通常意味着"当前执行用户的特定安全视图".它并不特指用户,因为用户这个词通常代表一个人.而在安全领域,Subject可以代表人同时也可以代表任何第三方进程,定时任务,守护进程或任何类似的内容,它仅仅代表和当前软件交互的东西,大多数情况下可以认为Subject
就是当前用户.getSubject()
方法是独立程序调用会返回一个Subject对象基于用户数据,在一个特定的应用环境下,或者在一个服务器环境下(比如web app),它需要基于用户数据的Subject
关联当前进程或访问的请求.现在你拥有了一个
Subject
对象,你可以用它做什么呢?如果你想在应用的当前session下设置或获取某些变量,你可以按照如下方式获取session:
Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );
17
这个Session(一次会话)是一个Shiro特定环境下的实例,它可以提供大多数你需要的在HttpSessions中的功能和数据,此外还有一个不同点就是它不需要HTTP环境!
如果Shiro部署在一个web应用中,默认
Session
会基于HttpSession.但是在非web环境,比如当前的简单教程应用,Shiro将自动使用它的企业级Session管理来自动生成Session.这代表以你可以使用同样的API在你的应用中,在任何层面,且与部署环境无关,它将打开应用的新世界,任何需要Session的应用都不再被强制使用HttpSession
EJB Stateful Session对象,而且现在任何客户端技术都可以共享session数据.所以现在你可以获得一个
Subject
和他们的会话.那些真正有用的东西呢?比如检查是否允许他们做一些事情,比如检查角色和权限?当然,我们可以只检查它们是否是已知用户,上述的
Subject
实例代表了当前用户,但谁是当前用户?现在我们还是匿名状态,直到登录至少一次,于是,我们这样做:
if ( !currentUser.isAuthenticated() ) {
// 以特定GUI的方式收集用户 主体 和 凭据
// 比如html格式的用户名和密码, X509认证, OpenID, 等.
// 我们在此使用最常用的用户名/密码
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 你只需要如下操作就可以实现记住我的功能(无需配置,内建功能!):
token.setRememberMe(true);
// 登录
currentUser.login(token);
}
18
完成了,不能再简单了!
但如果他们登录失败要如何处理?你可以捕捉所有有序的特定异常,这些异常将告诉你实际发生了什么,并允许你做出对应的处理和反应.
try {
currentUser.login( token );
// 如果没有异常,就到此结束
} catch ( UnknownAccountException uae ) {
// 用户名不存在
} catch ( IncorrectCredentialsException ice ) {
// 密码不匹配
} catch ( LockedAccountException lae ) {
// 账户被锁定,无法登陆
}
... 更多类型异常检查...
} catch ( AuthenticationException ae ) {
// 无法处理的异常
}
19
有很多你可以检查的异常类型,还可以自定义客户端异常类型假如Shiro里面的都不符合要求,可以在授权异常章节看到更多内容.
处理技巧
安全性最好的处理方法是给一个登陆失败提示,而不是具体的细节提示,因为你也不想帮助黑客黑进你的系统里面吧.
好的,到现在为止,我们已经有一个已经登陆的用户,之后要怎么做?
记录我是谁:
// 打印他们的用户主体,这里代表用户名
log.info( "用户 [" + currentUser.getPrincipal() + "] 登陆成功." );
20
我们可以测试看他们是否拥有某个角色:
if ( currentUser.hasRole( "schwartz" ) ) {
log.info("希望schwartz和你同在!" );
} else {
log.info( "你好,普通人." );
}
21
我们同样可以检查他们是否拥有某个特定实体或类型的一个权限.
if ( currentUser.isPermitted( "lightsaber:wield" ) ) {
log.info("你可以用光剑戒指。明智地使用它.");
} else {
log.info("对不起,光剑戒指只为schwartz主人服务.");
}
22
此外,我们还可以执行非常强大的实例级权限检查-查看用户是否有能力访问一种类型的特定实例:
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
log.info("你被允许驾驶winnebago车型eagle5.这是钥匙,玩的开心!");
} else {
log.info("对不起,你没有权限开 'eagle5' 房车!");
}
23
如此简单,不是吗?
最后我们的用户操作完成后他们可以如下退出:
currentUser.logout(); // 移除所有用户信息和已验证的用户关联的session等.
24
最终示例类
在上面的代码示例添加结束后,下面是我们最终示例代码.自由的编辑和玩吧,修改安全检查或INI配置文件,只要你喜欢:
最终 src/main/java/Tutorial.java
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.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Tutorial {
private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
public static void main(String[] args) {
log.info("我的第一个Apache Shiro应用");
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 获取当前访问的用户对象
Subject currentUser = SecurityUtils.getSubject();
// 使用Session做些事,不需要web或JEB环境
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("恢复为正确的值! [" + value + "]");
}
// 让我们登录看看当前用户有哪些角色和权限
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("根本就没有用户名: " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("账户密码 " + token.getPrincipal() + " 不正确!");
} catch (LockedAccountException lae) {
log.info("当前账户 " + token.getPrincipal() + "被锁定. " +
"请联系管理员解锁账户.");
}
// ... 抓取更多异常 (也许这里可以自定义你的应用)
catch (AuthenticationException ae) {
// 无法处理的情况,错误?
}
}
// 告诉我是谁:
// 打印他们的认证主体,默认是用户名
log.info("用户 [" + currentUser.getPrincipal() + "] 登录成功.");
// 测试角色:
if (currentUser.hasRole("schwartz")) {
log.info("Schwartz一直与你同在!");
} else {
log.info("你好,普通人");
}
// 测试权限类型,非实例类型
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("你可以使用光明戒指. 明智的使用.");
} else {
log.info("对不起,光明戒指只为schwartz主人服务.");
}
// 一个强大的实例级权限鉴定
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("你被允许驾驶winnebago车型eagle5.这是钥匙,玩的开心!");
} else {
log.info("对不起,你没有权限开 'eagle5' 房车!");
}
// 当所有操作都结束时,记得退出!
currentUser.logout();
System.exit(0);
}
}
25
总结
希望这个介绍示例帮助你理解如何设置Shiro于一个基本的应用中,就像Shiro的主要设计概念,Subject
和SecurityManager
..
但这只是一个极其简单的应用.你或许会问自己,如果我不想使用INI来配置用户使用,取而代之的是连接一个更加灵活的数据源要怎么做?
为了回答此问题需要更深的理解Shiro的结构和配置机制,我们将会在下一节讲解Shiro的配置.
网友评论