如果您是Apache Shiro的新手,这个简短的教程将向您展示如何使用Apache Shiro构建一个最初步的,简单的应用程序。我们将一路上讨论Shiro的核心概念,以帮助您熟悉Shiro的设计和API。
如果您不想在学习本教程时实际编辑文件,则可以获得几乎相同的示例应用程序并随时引用它。选择一个位置:
- 在Apache Shiro的Git存储库中:https://github.com/apache/shiro/tree/master/samples/quickstart
- 在Apache Shiro的源代码发布
samples/quickstart
目录中。
1、搭建
在这个简单的例子中,我们将创建一个非常简单的命令行应用程序,它将运行并快速退出,这样您就可以了解Shiro的API。
Apache Shiro的最初设计,就是支持任何应用程序 - 从最小的命令行应用程序到最大的集群Web应用程序。即使我们正在为本教程创建一个简单的应用程序,但要知道无论您的应用程序如何创建或部署,shiro都是以同样的方式使用。
搭建前置条件
- JDK版本在1.5及以上
- Maven 2.2.1及以上(maven并非是shiro要求的,只是示例使用了而已。您可以获得Shiro的jars并以您喜欢的方式将它们合并到您的应用程序中,例如使用Apache Ant和Ivy)
例如,现在创建一个maven项目,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>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<!-- This plugin is only to test run our little application. It is not
needed in most Shiro-enabled applications: -->
<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.1.0</version>
</dependency>
<!-- Shiro uses SLF4J for logging. We'll use the 'simple' binding
in this example app. See http://www.slf4j.org for more info. -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Tutorial 类
我们将运行一个简单的应用程序,因此我们需要使用public static void main(String[] args)
方法创建一个Java类。
在包含您的pom.xml
文件的同一目录中,创建一个src/main/java
子目录。在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);
}
}
测试运行
执行main方法,你会看到我们的小教程“应用程序”运行并退出。您应该看到类似于以下内容的东西(注意粗体文本,指示我们的输出):
运行应用程序[Tutorial.main()] INFO Tutorial - My First Apache Shiro Application
我们已经验证了应用程序运行成功 - 现在让我们启用Apache Shiro。
启用Shiro
在应用程序中启用Shiro时要首先要理解的是,Shiro中的几乎所有内容都与称为的中心/核心组件相关SecurityManager有关系
。对于那些熟悉Java安全性的人来说,这是Shiro关于SecurityManager的概念 - 它与java.lang.SecurityManager
不是一回事。
虽然我们将在架构章节中详细介绍Shiro的设计,现阶段只需要了解Shiro SecurityManager
是应用程序中Shiro环境的核心,并且每个应用程序都必须存在一个独立的SecurityManager
。因此,我们在Tutorial应用程序中必须做的第一件事是设置SecurityManager
实例。
配置
虽然我们可以直接实例化一个SecurityManager
类,但是Shiro的SecurityManager
实现有足够多的配置选项和内部组件,这使得直接用Java代码实例化变得很麻烦 - SecurityManager
使用灵活的基于文本的配置格式配置会容易得多。
为此,Shiro提供基于INI文本的默认的配置解决方案。人们现在已经厌倦了使用庞大的XML文件,INI易于阅读,易于使用,并且需要很少的依赖性。
Shiro的SecurityManager
实现和所有支持组件都兼容JavaBeans。这使得Shiro可以通过任何配置格式进行配置,如XML(Spring,JBoss,Guice等),YAML,JSON,Groovy Builder等。INI只是Shiro在任何其他配置方式不可用的环境下提供的最基础的配置格式。
shiro.ini
So we’ll use an INI file to configure the Shiro SecurityManager
for this simple application. First, create a src/main/resources
directory starting in the same directory where the pom.xml
is. Then create a shiro.ini
file in that new directory with the following contents:
因此,我们将在这个简单的应用程序中使用INI文件来配置SecurityManager
。首先,创建一个与pom.xml文件同级的目录
src/main/resources
。然后在该目录下创建shiro.ini文件,文件内容:
# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================
# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5
如您所见,配置文件中配置了一组静态用户帐号,用来在我们的第一个应用程序中使用。在后面的章节中,您将看到我们如何使用更复杂的用户数据源,如关系数据库,LDAP和ActiveDirectory等。
Now that we have an INI file defined, we can create the SecurityManager
instance in our Tutorial application class. Change the main
method to reflect the following updates:
现在我们已经定义了一个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);
}
在添加了3行代码后,我们的示例应用程序中启用了Shiro!
运行main方法,你会看到一切仍然运行成功(由于Shiro的默认日志等级是debug或者更低,所以你不会看到任何Shiro日志消息 - 如果它启动并运行没有错误,那么说明一切都正常)。
那么上述3行代码具体做了什么呢?
-
我们使用Shiro的
IniSecurityManagerFactory
实现来加载位于类路径根目录下的shiro.ini
。该实现反映了Shiro对工厂方法设计模式的支持。该classpath:
前缀是一个资源指示符,告诉shiro从哪里加载ini文件(其他前缀,如url:
和file:一样支持
)。 -
调用
factory.getInstance()
方法,该方法解析INI文件并返回配置的SecurityManager
实例。 -
在这个示例中,我们设置
SecurityManager为
可通过JVM访问的静态单例对象
。但是需要注意的是,如果在jvm上运行多个shiro应用程序时,这样做是不推荐的。当然对于这个简单的示例是没关系。但更复杂的应用程序通常会把SecurityManager放置在应用程序特定的内存中
(例如在Web应用程序ServletContext
或Spring,Guice或JBoss DI容器实例中)
使用Shiro
现在我们的SecurityManager已经准备就绪,现在我们可以开始做我们真正关心的事情 - 执行安全操作。
在保护我们的应用程序时,我们自问的最相关的问题可能是“谁是当前用户?”或“当前用户是否允许执行X”?在我们编写代码或设计用户界面时,通常会问这些问题:应用程序通常是基于用户需求构建的,并且您希望根据每一个用户来展示不同功能。因此,我们在应用程序中考虑安全性的最自然方式是基于当前用户。Shiro的API从根本上用Subject
概念代表了“当前用户” 的概念。
在几乎所有环境中,您都可以通过以下调用获取当前正在执行的用户:
Subject currentUser = SecurityUtils.getSubject();
用SecurityUtils.
getSubject(),我们可以获取当前正在执行的Subject
。Subject是一个安全术语,基本上是指“当前正在执行的用户的特定于安全性的视图”。它不被称为“用户”,因为“用户”这个词通常与人类相关联。在安全领域,术语“主体”可以指人类,也可以指第三方流程,定时任务,守护程序帐户或任何类似的行为。它只是意味着“当前与软件交互的东西”。但是,为了理解,您可以将其Subject
视为Shiro的“用户”概念。
在
独立应用程序中调用getSubject()
可以返回携带特定位置的用户数据的Subject对象
,并且在服务器环境(例如,web应用程序)中,它会获取基于与当前线程或传入请求相关联的用户数据的Subject。
现在你有了Subject
,你能用它做什么?
如果您希望在当前会话中获取信息,则可以获取其会话:
Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );
这session是shiro下的特定的实例,它提供了常规httpSession所用的大部分内容,但是同样也提供了很多其他东西,并且还有一个很大的区别是,它不需要HTTP环境!
当shiro集成在web应用中,默认情况下,session会基于httpSession构建。但是在非Web环境,像我们的示例中,shiro会默认自动使用企业session管理,这意味着在不同的环境下,你可以通使用同样的API。这打开了一个全新的应用程序世界,因为任何需要会话的应用程序都不需要被强制使用HttpSession
或EJB有状态会话Bean。而且,任何客户端技术现在都可以共享会话数据。
所以现在你可以获得一个Subject
和他们的Session
。那些真正有用的东西呢,比如检查是否允许他们做某事,比如检查角色和权限?
好吧,我们只能为已知用户进行检查。上述的Subject
代表当前用户,但谁是当前用户?好吧,他们是匿名的 - 也就是说,直到他们至少登录一次。所以,让我们这样做:
if ( !currentUser.isAuthenticated() ) {
//collect user principals and credentials in a gui specific manner
//such as username/password html form, X509 certificate, OpenID, etc.
//We'll use the username/password example here since it is the most common.
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(true);
currentUser.login(token);
}
可以看到上述代码很简单。但如果他们的登录尝试失败怎么办?您可以捕获各种特定的异常,它们可以准确地告诉您发生了什么,并允许您相应地处理和做出反应:
try {
currentUser.login( token );
//if no exception, that's it, we're done!
} catch ( UnknownAccountException uae ) {
//username wasn't in the system, show them an error message?
} catch ( IncorrectCredentialsException ice ) {
//password didn't match, try again?
} catch ( LockedAccountException lae ) {
//account for that username is locked - can't login. Show them a message?
}
... more types exceptions to check if you want ...
} catch ( AuthenticationException ae ) {
//unexpected condition - error?
}
您可以检查许多不同的一场,或者可以自定义异常。
好的,所以到现在为止,我们已经登录了用户。我们还能做什么?
他们是谁:
//print their identifying principal (in this case, a username):
log.info( "User [" + currentUser.getPrincipal() + "] logged in successfully." );
他们是否具有特定的角色:
if ( currentUser.hasRole( "schwartz" ) ) {
log.info("May the Schwartz be with you!" );
} else {
log.info( "Hello, mere mortal." );
}
他们是否有权对某种类型的实体采取行动
if ( currentUser.isPermitted( "lightsaber:weild" ) ) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
实例级权限检查 - 能够查看用户是否能够访问类型的特定实例:
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
最后,当用户完成使用该应用程序后,他们可以注销:
currentUser.logout(); //removes all identifying information and invalidates their session too.
完整的 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");
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:weild")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
currentUser.logout();
System.exit(0);
}
}