随笔 - 41  文章 - 7  trackbacks - 0
<2016年8月>
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910

常用链接

留言簿

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

原文:http://shiro.apache.org/reference.html
Apache Shiro介绍
Apache Shiro是什么?
Apache Shiro 是一个可干净处理认证,授权,企业会话管理以及加密的强大且灵活的开源安全框架.
Apache Shiro的首要目标是易于使用和理解. 安全可以是非常复杂的,有时甚至是痛苦的,但它不是.
框架应该隐藏复杂的地方,暴露干净而方便的API,以简化开发者的努力进而使其程序安全.
下面是Apache Shiro可以做的事情:
  • 认证用户的身份
  • 实施用户访问控制,如:
  • 确定用户是否分配了某个角色
  • 确定用户是否允许做某些事情
  • 任何环境中均可使用Session API, 即使是非web环境或EJB容器
  • 反映在认证,访问控制,或会话生命期之间的事件.
  • 聚合一个或多个用户安全数据数据源,以将其作为单一的组合用户视图.
  • 可开启Single Sign On (SSO) 功能
  • 为用户无须再次登录开启 'Remember Me'服务
...
还有更多-所有都集成在紧密结合的易于使用的API中.
Shiro试图在所有应用环境中都实现这些目标 - 从最简单的命令行应用程序到大型企业应用程序,它不强制依赖于第三方框架,容器,或应用程序服务器.
当然,在可能的情况下,也可以将其整合到这些环境中, 但它在任何环境中都是开箱即可使用的.
Apache Shiro 特性
Apache Shiro是一个包含多种特性的综合应用安全框架。下图展示了Shiro的主要特性,本参考手册也会参照下面的特性来组织内容:

Shiro的目标(Shiro开发团队称其为“四大应用安全基石”)是认证、授权、会话管理、加密:
  • 认证: 有时称为登录, 用于验证用户身份. 
  • 授权: 访问控制过程,如,确定谁能访问什么. 
  • 会话管理: 管理特定用户会话,即使是非web环境或EJB应用程序 
  • 加密: 虽使用加密算法来保证数据安全, 但仍然易于使用.
也有其它功能,以在不同的应用环境中支持和加强这些特性,特别是:
  • Web 支持 — Shiro的web支持APIs可帮助你容易地保护web应用程序安全.
  • 缓存 — 缓存是在Apache ShiroAPI的一等公民,在确保安全操作的同时能确保快速和有效。
  • 并发性- Apache Shiro的并发特性被设计为支持多线程的应用。
  • 测试 —测试支持简化了单元和集成测试的创建,并确保您的代码如预期一样的安全。
  • "Run As" — Shiro 允许用户以其它用户的身份(如果有资格的话)来运行, 这种特性在管理场景中非常有用.
  • "Remember Me" — Shiro 可以跨会话来记住用户身份,这样只在强制的情况下才需要用户登录.
第一个 Apache Shiro 应用程序
如果你从未接触过Apache Shiro,这篇简短的指南会向你展示如何使用Apache Shiro来设置一个初始和非常简单的安全web应用程序. 我们将讨论Shiro的核心概念来帮助您熟悉Shiro的设计和API。
如果你不想遵从本指南来编辑文件,你可以从下面的位置来选择获取几乎相同的示例程序,并按照你的方式来引用:
设置
在这个简单的例子中, 我们会创建一个非常简单的快速运行和退出的命令行应用程序, 这只是为了让你对 Shiro的API有所感觉.
任何应用程序
Apache Shiro从最初就被设计用于支持任何应用程序- 从最小的命令行程序到大型集群web应用程序. 虽然在本指南中我们创建的是一个简单app,但不管应用程序是如何创建或无论部署在什么地方,都可以应用同样的使用模式.
本指南需要Java 1.5+.同时也会使用 Apache Maven 作为我们的构建工具,当然对于Apache Shiro的使用,这不是必须的.你可以获取Shiro的jars,并以你喜欢的方式来进行组合,例如可以使用 Apache AntIvy.
对于本指南,请确保你使用的是Maven 2.2.1+. 你可以在命令中输入mvn --version查看类似于下面的东西:
Testing Maven Installation
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"
现在,在你的文件系统中创建一个新目录,例如, shiro-tutorial ,并将下面的pom.xml 文件放在那个目录中:
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子目录,并使用下面的内容在其中创建一个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);
}
}
现在不用担心import语句- 我们很快就会讲解它们. 现在,我们已经有了一个典型的命令行程序外壳. 程序会打印文件"My First Apache Shiro Application"并退出.
测试运行
要测试我们的Tutorial应用程序,切换到tutorial根目录(即.shiro-tutorial),在命令行提示窗口中执行下面的命令:
mvn compile exec:java
然后你将看到我们的Tutorial 程序运行和退出.你可能会看到与下面相似的内容(注意,粗文本表示我们的输出):
运行程序
lhazlewood:~/projects/shiro-tutorial$ mvn compile exec:java
... a bunch of Maven output ...
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安全的人来说,这是Shiro的SecurityManager概念 -它不同于java.lang.SecurityManager.
Architecture 章节中,我们会详细讲解Shiro的设计,对于现在来说,只需要知道Shiro SecurityManager 是Shiro 环境中的核心且每个程序都必须存在一个SecurityManager 就足够了.
因此,在Tutorial 程序中,我们要做的第一件事就是建立SecurityManager实例.
配置
虽然我们可以直接实例化SecurityManager类, Shiro的SecurityManager 实现包含足够的配置选项和内部组件,使其可以从Java源代码中进行构建- 但可以使用灵活的文本配置格式来更简单地配置SecurityManager.
为此, Shiro通过基于文本的 INI 配置提供了一个缺省的公共解决方案 . 现在,人们都厌倦了使用笨重的XML文件,INI易于阅读,简单易用,并且需要极少依赖。稍后你也会看到,用一个简单的对象图导航,INI可像SecurityManager一样有效地配置简单的对象图.
多个配置选项
Shiro的 SecurityManager实现以及所有支持组件都是JavaBeans 兼容的. 这允许Shiro使用特定的配置格式来进行配置,配置格式如 XML (Spring, JBoss, Guice, etc), YAML, JSON, Groovy Builder 标记等等.
INI只是Shiro的共同解决方案,它允许在任何环境中进行配置.
shiro.ini
对于这个简单应用程序,因此我们使用INI文件来配置Shiro SecurityManager.首先,在与pom.xml相同的目录中,创建一个 src/main/resources 目录. 然后使用下面的命令,在新创建的目录中创建一个shiro.ini文件:

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等等.
引用配置
现在我们已经定义了一个INI文件, 然后我们就可在Tutorial类中创建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!是不是很简单?
运行 mvn compile exec:java能看到一切都能成功运行(因为Shiro默认为debug或更低级别,你不会看到任何Shiro日志消息-如果启动或运行时不出现错误,那么一切就工作得很好).
以下是上面增加代码所做的事情:
  1. 我们使用Shiro的IniSecurityManagerFactory实现来获取位于classpath根下的shiro.ini文件.此实现了反映了Shiro对工厂设计模式的支持. classpath: 前辍是一个资源标识符,用于告诉shiro在什么位置加载ini文件(也支持其它前辍,像url: 和file: ). 
  1. 然后调用了factory.getInstance()方法,它会解析INI文件,并返回一个反映配置的SecurityManager实例. 
  1. 在这个简单示例中,我们将SecurityManager设为了一个可通过JVM来访问的static (memory) 单例.请注意,如果在单个虚拟机中存在多个启用了Shiro的应用程序,这是不理想的.对于这个简单示例,它是OK的, 但在更复杂的应用程序环境中,通常应该将SecurityManager放置在特定应用的内存中(如,放在web app的ServletContext中或Spring, Guice或 JBoss DI 容器实例中).
使用Shiro
现在我们建立好了SecurityManager,可以准备使用了, 现在让我们做一些我们真正关心的东西 - 执行安全操作.
当为我们的应用程序提供安全时,我们通常问自己最相关的问题是"当前用户是谁?”或“是否允许当前用户做某些事情”? 这是经常会问的问题,因此我们会编写代码并设计我们的接口:
应用程序通常会基于用户存储进行构建, 并且你希望每个用户都是安全的. 因此,对于我们来说,最自然的方式是认为应用程序的安全是基于当前用户的. Shiro的 API从根本上使用了Subject概念来表达当前用户.
在几乎所有环境中,你可以通过下面的调用来获取当前执行用户:
Subject currentUser = SecurityUtils.getSubject();
使用SecurityUtils.getSubject(), 我们能获取当前执行的SubjectSubject是一种安全术语,其基本意义为"当前执行用户的特定安全视图". 它不称为'User',因为 'User' 这个词通常与人类联系在一起.
在安全世界中,术语'Subject'可以表示人类,但也可以表示第三过程, cron job, 后台帐户,或任何相似的东西.它简单地表示为当前与软件交互的事物.
虽然这只是针对大多数的意图和目的,你仍然可将Subject认为是Shiro的'User'.
在一个独立的应用程序中,getSubject()方法会返回在应用特定位置用户数据的基础上返回一个Subject,在服务器环境中(如. web app),它会基于当前线程或传入请求相关的用户数据中来获取Subject.
现在你有了Subject,你能用它来做什么呢?
如果你想在应用程序其会话期间对当前用户做些什么,你可以获取它们的会话:
Session session = currentUser.getSession(); session.setAttribute( "someKey", "aValue" );
Session是Shiro特有的实例,它提供了类似常规HttpSessions大部分的功能,但也存在额外的东西以及一个重大区别:它不需要HTTP环境!
如果部署在web应用程序中,默认情况下,Session是基于HttpSession的. 但在非web环境中,就像这个简单的tutorial程序, Shiro会默认自动使用其企业级会话管理.
这意味着,无论是哪种部署环境,无论在哪一层,在你的应用程序中都可以使用同一套API. 这打开了应用程序的新世界,因为任何应用程序都可以不强制使用 HttpSession或EJB有状态会话bean的前提下获取Session. 除此之外,任何client技术现在都可以共享会话数据.
所以现在你可以获取Subject及其会话了.像检查它们是否允许做某些事情以及检查其角色与权限这样真正有用的事情又怎样呢?
我们只能为一个已知的用户做这些检查。上面的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?
}
你可以捕获多种不同类型的异常,或者抛出Shiro未出现的自定义异常.参考AuthenticationException JavaDoc 来了解更多.
温馨提示
最佳安全实践是向用户提示一般的登录失败消息,因为你不想帮助攻击者试图闯入你的系统.
Ok, 到现在为止,我们有了一个登录用户.还有什么事情可以做呢?
让我们看看它是谁:
//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类
在添加了上述代码后,以下是最终的Tutorial类文件.只要你喜欢,你可以随意编辑,并修改安全检查(和INI配置)来玩它:
Final 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");
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);
}
}
总结
希望这本入门教程帮助你了解如何在一个基本应用中设置Shiro,以及Shiro的主要设计理念 Subject 和SecurityManager。
但这是一个相当简单的应用。你可能会问自己,“如果我不想使用INI用户帐户而是要连接到一个更复杂的用户数据源,又该怎么做呢?”
要回答这个问题,需要深入一点了解Shiro的架构和支持的配置机制。我们将下一个Shiro的Architecture 进行讲解.
Apache Shiro 架构
Apache Shiro的设计目标是通过直观而简单的方式来简化应用程序安全的开发. Shiro的核心设计模型-在一个上下文里与应用程序进行交互.
软件应用程序程序通常是基于用户存储来设计的. 也就是说,你会经常设计用户如何与软件进行交互的接口或服务.
例如,你可能会说, "如果用户与应用程序交互时已经登录了,我应该向其展示一个按钮,它们可以点击来查看它们的帐户信息.如果它们没有登录,我将向它们展示一个登录按钮."
此示例语句暗示了应用程序在很大程度上是为了满足用户的需求而编写的。即使'用户'是另一个软件系统,而不是一个人,你仍然要编写代码来反映谁(或什么)目前正在与您的软件交互。
高层次概述
在最高概念层面,Shiro的架构包含了3个主要概念:Subject, SecurityManager 和 Realms. 下面的图表是这些组件如何交互的高级概述,我们将覆盖下面的每一个概念:



Subject:
正如我们在 Tutorial中提到的, Subject本质上当前执行用户的特定安全视图.
"User”往往意味着一个人类,一个Subject可以是一个人,但它也可能代表一个第三方服务,后台账户,cron作业,或任何类似的东西-基本上是当前与软件交互的任何东西. 
Subject 实例都绑定在SecurityManager上. 当你与Subject交互时,这些交互都会转换成特定subject与SecurityManager的交互. 
SecurityManager:
SecurityManager 是Shiro架构的心脏,它扮演了一种保护伞(umbrella)对象,协调其内部的安全组件共同形成一个对象图.
然而,一旦应用程序配置了SecurityManager及其内部对象图,它就会退居幕后,应用开发人员几乎把他们的所有时间都花在Subject API调用上。
后面我们会详细讨论SecurityManager, 但要意识到,当你与Subject交互时,其实是幕后的SecurityManager在执行Subject的安全操作.这可以从上面的图中得到反映.
Realms:
Realms扮演的是Shiro与应用程序安全数据的桥梁或连接器.
当它与像用户帐户这样的相关安全数据进行实际交互时,如执行认证(login) 和授权(访问控制), Shiro会查看为应用程序中配置的一个或多个Realms. 
从这种意义上说,Realm本质上是一个带有安全特征的数据访问对象(DAO): 它封装了数据源的连接细节,并按需将相关数据提供给Shiro.
当配置Shiro时,你必须至少指定一个Realm来用于验证或授权. SecurityManager可配置多个Realms, 但至少要配置一个. 
Shiro提供了多个开箱即用的Realms来连接多种安全数据源,如LDAP, 关系型数据库(JDBC),像INI和属性文件这样的文本配置源.
如果默认的Realms不能满足你的需要,你可以插入你自己的Realm实现来表示自定义数据源. 
像其它内部组件一样, Shiro SecurityManager会对如何使用Realms来获取安全身份数据(表示为Subject实例)进行管理.
详细架构
下图展示了Shiro的核心架构概念以及每种概念的简短概述:

与软件进行交互的实体(用户,第三方服务,cron作业等等)特定安全视图. 
正如上面提到的, SecurityManager是Shiro架构的心脏.它主要充当保护伞对象,协调其管理组件来确保工作顺利进行. 同时,它也会管理每个应用程序用户的Shiro视图,因此它知道如何执行每个用户上的操作.
认证负责验证用户的登录. 当一个用户试图登录时,认证将执行验证逻辑. 认证组件知道如何协调一个或多个存储相关用户信息的. 从Realms中获取的数据用于验证用户的身份,以确保其合法性.
如果配置了多个Realm, AuthenticationStrategy 会协调Realms来确定在哪些情况下认证是成功或是失败的(例如,如果某个realm是成功的,但其它是失败的l,那么认证是成功的吗?
是否需要所有realms都成功?还是只需要第一个成功?). 
授权是在应用程序中负责确定访问控制的组件.最终说来它只是允许用户做什么或不做什么的机制.类似于Authenticator,Authorizer也知道如何协调多个后端数据源来访问角色和权限信息.
Authorizer可使用这些信息来决定一个用户是否允许来执行给定的操作. 
SessionManager知道如何来创建和管理用户生命周期,以为所有环境中的用户提供强大的会话体验. 在安全框架的世界中,这是独特的特性- Shiro有管理任何环境中用户会话的能力, 即使这些环境处于非Web/Servlet或在EJB容器环境.
默认情况下,如果可行的话,Shiro会使用现有的会话机制, (例如. Servlet容器),但如果不存在这样的环境,如独立程序或非web环境,它会使用内置的企业会话管理来提供相同的编程体验. SessionDAO的存在允许使用任何数据源来存储会话. 
SessionDAO可代表SessionManager来执行会话持久化操作(CRUD).这允许在会话管理框架中插入任何数据存储. 
缓存管理器用于创建和管理其它Shiro组件使用的缓存实例生命周期.因为Shiro在执行认证,授权和会话管理时,可访问多个后端数据源,当使用这些数据源时,缓存通常是框架中提高性能的最好架构特征.
任何一个现代开源或企业级缓存产品都可以插入到Shiro中,以提供快速,高效的用户体验. 
加密是企业级安全框架中一个很自然的功能. Shiro的加密包包含了易于使用和理解的加密 Ciphers, Hashes (aka digests)表现以及不同的编码实现.
包中的所有类都是精心设计为易于使用和理解. 任何使用过Java本地加密的人,都可以轻易地驾驭它.
Shiro的加密APIs简化了复杂的Java机制,使得加密对于普通人来说也能简单使用. 
正如上面所提到的, Realms扮演的是Shiro与应用程序安全数据之间的桥梁或连接器.
当它与像用户帐户这样的相关安全数据进行实际交互时,如执行认证(login) 和授权(访问控制), Shiro会查看为应用程序中配置的一个或多个Realms.
你可以根据需要来配置多个Realms  (通常情况下一个数据源一个Realms) ,Shiro会按照需要来为认证和授权协调数据.
SecurityManager
因为Shiro的API鼓励使用以Subject为中心的编程模型,大部分应用程序开发者会极少直接与SecurityManager交互(框架开发者有时可能会发现它很有用).
即使是这样,了解SecurityManager的功能,特别是在为应用配置时, 也是极其重要的.
设计
如前所述,应用程序安全管理器会为所有应用程序用户执行安全操作和管理用户状态. 在Shiro的默认SecurityManager实现中包含:
  • Authentication(认证)
  • Authorization(授权)
  • Session Management(会话管理)
  • Cache Management(缓存管理)
  • Realm coordination(Realm协作)
  • Event propagation(事件传播)
  • "Remember Me" Services(记住我服务)
  • Subject creation(Subject创建)
  • Logout(登出)
等等.
但在单个组件中,管理着多个功能. 而且,如果一切功能都集中到单一的实现类中,会使这些操作变得灵活和可定制化是相当困难的.
为了简化配置和开启灵活的配置性/可插拔性, Shiro的实现设计上是高度模块化的- SecurityManager实现及其类层次并没有做这么多工作.
相反,SecurityManager实现几乎充当的是轻量级容器组件, 几乎把所有行为/操作都委派给了内嵌/包装组件.上面的详细框架图就反映了这种包装设计.
虽然是由组件来实际执行逻辑,SecurityManager实现知道如何以及何时来协调组件来完成正确行为.
SecurityManager 实现同时也是JavaBeans兼容的,这允许你通过标准的Java访问器/修改器方法(get*/set*)来轻松地定制可插拔组件.
这意味着Shiro的架构模块可以很容易配置自定义行为。
简单配置
由于JavaBeans的兼容性, 使其很容易地通过支持JavaBeans的自定义组件来配置SecurityManager,如Spring, Guice, JBoss等等.
下面我们就会涵盖 Configuration .
Apache Shiro 配置
Shiro设计为可工作于任何环境, 从简单的命令行应用程序到大型企业集群应用程序. 由于环境的多样性,存在着多种配置机制. 本章讲述Shiro核心支持的配置机制.
多个配置选项
Shiro的SecurityManager实现以及所有支持组件都是与JavaBeans兼容的.这允许Shiro使用特定配置格式,如常规Java, XML (Spring, JBoss, Guice, etc), YAML, JSON, Groovy Builder标记来配置.
编程配置
创建SecurityManager并使用可用的最简单方式是创建一个org.apache.shiro.mgt.DefaultSecurityManager. 例如:
Realm realm = //实例化或获取一个Realm实例.我们后面会讨论Realms. SecurityManager securityManager = new DefaultSecurityManager(realm); //通过静态内存,使SecurityManager 实例对整个应用程序可用: SecurityUtils.setSecurityManager(securityManager);
令人惊讶的是,虽只有3行代码,但你现在有了一个适合于许多应用且功能齐全的Shiro环境。多么容易!
SecurityManager 对象图
正如在Architecture章节中讨论的, Shiro的SecurityManager实现本质上内嵌特定安全的组件模块对象图.
因为它们也是JavaBeans兼容的,你可调用任何内嵌组件上的 getter和setter方法来配置SecurityManager和它的内部对象图.
例如,如果你想配置SecurityManager实例来使用自定义的SessionDAO来定制 Session Management,你可以直接使用内置的SessionManager的setSessionDAO来设置SessionDAO:
...
DefaultSecurityManager securityManager =
new DefaultSecurityManager(realm);
SessionDAO sessionDAO =
new CustomSessionDAO();
((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO);
...
使用直接方法调用,你可以配置SecurityManager的对象图的任何部分.
虽像程序化定制一样简单,但它并不代表大多数现实世界应用程序的理想配置。
有以下几个原因为什么编程配置可能不适合您的应用程序:
  • 它需要你了解和实例化一个直接实现.如果不需要了解具体和查找位置,这是很好的. 
  • 由于Java的类型安全特性,你需要在get*方法中对特定实现进行类型转换. 因此太多的转换是丑陋的,冗长的,并紧密耦合了你的实现类.
  • SecurityUtils.setSecurityManager 方法调用使得实例化了的SecurityManager实例化了一个 VM 静态单例, 在这里是很好的,但对于多数应用程序来说,如果在同一个JVM中运行了多个开启了Shiro的应用程序,将会引发问题.本身来说,如果实例是应用程序级单例的,这会更好,但不是静态内存引用. 
  • 为了让Shiro配置生效,每次你都需要重新编译代码.
但,即使有这些警告,直接编程操作方案对于内存受限的环境,仍然是价值的,像智能手机应用程序.如果你的应用程序不会运行内存受限环境中,你会发现基于文本的配置更加简单和阅读.
INI 配置
大部分应用程序都受益于基于文本的配置,这样可以独立于源码进行修改,甚至使得那于不熟悉Shiro API的人来说,也更易于理解.
为了确保通用基于文本配置的解决方案能工作于任何环境(有很小的依赖),Shiro支持使用 INI format 来构建SecurityManager 对象图和它的支撑组件.
INI易于阅读和配置,并可以简单建立而适用于大部分应用程序.
从INI创建SecurityManager
下面有两个例子来讲述如何基于INI配置来构建SecurityManager.
从INI资源来构建SecurityManager
我们可以从INI资源路径来创建SecurityManager实例. 资源可通过文件系统, classpath,或URLs来获取,此时其前辍分别为file:, classpath:, 或 url: .
这个例子使用Factory来从classpath根路径下来摄取shiro.ini文件,并返回SecurityManager实例:

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.config.IniSecurityManagerFactory;
...
Factory<
SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
从INI实例来构建SecurityManager
INI配置也通过编程方式来构建,即通过org.apache.shiro.config.Ini 类. Ini 类的功能类似于 JDK java.util.Properties 类,但还额外支持分段名称的分割.
For example:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.config.Ini;
import org.apache.shiro.config.IniSecurityManagerFactory;
...
Ini ini =
new Ini(); //按需填充 Ini实例
...
Factory<
SecurityManager> factory = new IniSecurityManagerFactory(ini);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
现在,我们知道如何从INI配置来构建一个SecurityManager, 让我们明确地了解如何定义一个Shiro INI配置.
INI 分段
INI是一个包含由唯一分段组织的key/value对的文本配置. 每个段中Keys是唯一的,而不是整个配置(这一点不像JDK Properties).
每个段都可像单个Properties定义进行查看.
注释行可以#或;开头
下面是分段的例子:
# ======================= # Shiro INI configuration # =======================
[main]
# 这里用于定义对象及其属性,如securityManager, Realms 以及任何需要用于构建
SecurityManager的配置
[users]
# 'users' 分段用于简单部署
# 当你只需要少量静态定义的用户帐户时
[roles]
# 'roles' 分段用于简单部署
# 当你只需要少量静态定义的角色时
[urls] # 'urls' 分段用于web应用程序中作基于url的基础安全控制
# 我们会Web文档中讨论这个分段
[main]
[main]分段是你配置应用程序SecurityManager实例,及其任何依赖,如Realms.
像SecurityManager或其依赖这样的配置对象实例听起来对于INI来说,是一件困难的事情,因为在这里我们只能使用name/value对.
但是通过一点点的约定和对对象图的理解,你会发现你可以做得很好。Shiro用这些假设使一个简单的配置机制成为了可能。
我们常喜欢把这种方法称为“穷人的“依赖注入,虽然不比全面的Spring/ Guice / JBoss的XML文件强大,但你会发现它没有过多的复杂性。
当然也可使用其他的配置机制,但对于Shiro来说,他们不是必须的.
只是为了刺激你的欲望,这里有一个有效的[main]配置实例:
[main]
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
myRealm = com.company.security.shiro.DatabaseRealm
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
myRealm.password = secret
myRealm.credentialsMatcher = $sha256Matcher
securityManager.sessionManager.globalSessionTimeout = 1800000
定义对象
细想下面的[main] 分段片断:
[main] myRealm = com.company.shiro.realm.MyRealm ...
这行实例化了一个com.company.shiro.realm.MyRealm类型的新对象,并为了以后的引用和配置,使其在名称myRealm名称下可用.
如果实例的对象实现了org.apache.shiro.util.Nameable接口, 那么可在对象上使用名称值(myRealm)来调用Nameable.setName.
设置对象属性
原始类型值
简单原始类型属性可以使用=号来赋值:
...
myRealm.connectionTimeout = 30000
myRealm.username = jsmith ...
这些行的配置转换成方法调用为:
...
myRealm.setConnectionTimeout(30000);
myRealm.setUsername(
"jsmith"); ...
这怎么可能呢?它假定所有的对象都是java bean兼容的POJOs。
在后台,Shiro默认使用Apache Commons BeanUtils来设置这些属性。所以尽管INI值是文本,BeanUtils知道如何将字符串值的原始类型,然后调用相应的JavaBean的setter方法。
引用值
如果需要设置的值不是原始类型而是其它对象呢? 你可以使用$来引用先前定义的实例.例如:
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
myRealm.credentialsMatcher = $sha256Matcher ...
这会简单地定位名为sha256Matcher 的对象,然后使用 BeanUtils 在 myRealm 实例上来设置这个对象(通过调用myRealm.setCredentialsMatcher(sha256Matcher)方法).
内嵌属性
在=的左边使用点号分隔法来对内嵌属性进行设值.例如:
...
securityManager.sessionManager.globalSessionTimeout = 1800000 ...
用BeanUtils转换成下面的逻辑为:
securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
图的遍历可以根据需要来设置深度: object.property1.property2....propertyN.value = blah
BeanUtils Property Support
Any property assignment operation supported by the BeanUtils.setProperty method will work in Shiro's [main] section, including set/list/map element assignments.
See the Apache Commons BeanUtils Website and documentation for more information.
Byte 数组值
由于原始字节数组不能文本格式进行指定,我们必须使用字节数组的文本编码.这些值可以通过Base64 编码字符串(默认)或 Hex编码字符串进行指定.
默认是Base64 ,因为 Base64编码需要更少的实际文本来表现值- 它有一个更大的编码字母表,这意味着你的令牌更短(对于文本配置更好).
# The 'cipherKey' attribute is a byte array. By default, text values
#
for all byte array properties are expected to be Base64 encoded:
securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA== ...
然而,如果你选择使用Hex编码, 你必须使用0x ('zero' 'x')作为字符串的前辍:
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
Collection属性
Lists, Sets 和Maps 也可以像其它属性来设置 -要么直接设置,要么通过内嵌属性设置. 对于sets和Lists,只需要以逗号分隔来指定值或对象引用的集合:
sessionListener1 = com.company.my.SessionListenerImplementation
...
sessionListener2 = com.company.my.other.SessionListenerImplementation
...
securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
对于Maps,你可以以逗号分隔的key-value对列表来指定, 每个key-value对都由冒号来分隔
object1 = com.company.some.Class
object2 = com.company.another.
Class
...
anObject = some.class.with.a.Map.property
anObject.mapProperty = key1:$object1, key2:$object2
In the above example, the object referenced by $object1 will be in the map under the String key key1, i.e. map.get("key1") returns object1. You can also use other objects as the keys:
anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2 ...
注意事项
顺序影响
虽然INI格式非常方便和易于理解,但它并没有像其它基于text/XML配置机制强大. 使用上述机制时,要理解的最重要的事情是顺序带来的影响.
小心
Each object instantiation and each value assignment is executed in the order they occur in the 在每个[main] 分段中,每个对象实例和及值的分配都是出现顺序来执行的.
这些行最终都会转换成对avaBeans getter/setter 方法的调用,且这些方法也是按相同顺序来调用的!在编写配置时,要当心.
覆盖实例
任何对象都可以通过配置文件后面新定义的实例来覆盖.因此对于下面的例子,第二个myRealm定义将覆盖前面一个:
...
myRealm = com.company.security.MyRealm
...
myRealm = com.company.security.DatabaseRealm ...
译者注:如果这两个key在同一个段内,就与先前的描述违背了.
这个导致myRealm成为com.company.security.DatabaseRealm的实例,先前的实例将不再使用(垃圾回收掉).
默认SecurityManager
你可能已经注意到了,上面的例子中没有定义 SecurityManager 实例, 我们跳到右边只设置了一个内嵌属性:
myRealm = ...
securityManager.sessionManager.globalSessionTimeout = 1800000 ...
这是因为securityManager实例是特定的一个- 它已经为你实例化好了,因此你不需要了解具体的SecurityManager实现.
当然,如果你想指定你自己的实现,你可以定义你的实现来进行覆盖:
... securityManager = com.company.security.shiro.MyCustomSecurityManager ...
当然,极少需要这样- Shiro的SecurityManager实现是可充分定制的,通常可以按任何需要进行配置.
[users]
[users] 分段允许你定义一些静态用户帐户集合. 这在一个非常小的用户帐户或用户帐户不需要在运行时动态创建环境中有用. 下面是例子:
[users]
admin = secret lonestarr = vespa, goodguy, schwartz darkhelmet = ludicrousspeed, badguy, schwartz
Automatic IniRealm
只定义非空的[users]或[roles]分段会自动触发org.apache.shiro.realm.text.IniRealm实例的创建,并使得[main]分段下的iniRealm可用.
你可以像上面一样来配置其它任何对象.
行格式
[users]分段中的每行都有下面的格式:
username = password, roleName1roleName2, ..., roleNameN
  • 等号左边的值是用户名
  • 等号右边的第一个值是用户的密码.密码是必须的.
  • 密码后面以逗号分隔的值是为用户分配的角色.角色名称是可选的.
密码加密
如果你不想[users]分段中的密码是明文的,你可以使用你喜欢的hash算法(MD5, Sha1, Sha256)来对其加密,并使用结果字符串来作为密码值.
默认情况下期望的密码字符串是Hex编码的,但你可以使用Base64编码来代替(见下面).
Easy Secure Passwords
为了节省时间, 你可以使用Shiro的 Command Line Hasher, 它会将密码hash成其它类型的资源.对于加密 INI [users] 密码是非常方便的.
一旦你指定了哈稀化的文本值, 你必须告诉Shiro它们是加密的. 你可为隐式创建的iniRealm 在[main]分段中来配置适当的对应你指定的哈希算法的CredentialsMatcher实现:
[main]
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
iniRealm.credentialsMatcher = $sha256Matcher
...
[users]
# user1 = sha256-hashed-hex-encoded password, role1, role2,
...
user1 = 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b, role1, role2, ...
你可在CredentialsMatcher上配置任何属性,如反映哈稀策略的对象,例如, 是否使用盐(salting)或者执行多少次hash迭代.
参考org.apache.shiro.authc.credential.HashedCredentialsMatcher JavaDoc来更好的理解哈稀策略.
例如,如果你想对用户密码使用Base64编码来代替默认的Hex,你可以按如下来指定:
[main]
...
# true = hex, false = base64:
sha256Matcher.storedCredentialsHexEncoded =
false
[roles]
[roles]分段允许你使用Permissions 来关联定义在[users]分段中的角色.
再次说明,这对于少量角色或在运行时不需要动态创建角色的环境有用.下面是一个例子:
[roles]
# 'admin' 的角色拥有所有权限, 使用通配符'*'来表示 admin = *
# 'schwartz' 角色可在任何lightsaber上执行任何操作(*): schwartz = lightsaber:*
# 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with # license plate 'eagle5' (instance specific id) goodguy = winnebago:drive:eagle5
行格式
[roles]分段中的每行必须按下面的格式来定义role-to-permission(s) key/value:
rolename = permissionDefinition1permissionDefinition2, ..., permissionDefinitionN
这里的permissionDefinition 是任意的字符串,但大部分人为了灵活性,会使用符合org.apache.shiro.authz.permission.WildcardPermission 格式的字符串. 参考Permissions 文档来了解更多关于权限的信息
内部逗号
注意,如果在单个permissionDefinition 中存在逗号分隔符(如. printer:5thFloor:print,info),你必须使用双引号,以避免解析错误:
"printer:5thFloor:print,info"
无权限的角色
如果角色不需要关联权限,那么则不需要在[roles]分段中列出.如果角色不存在,只需要在[users]分段中定义角色就足够创建角色了.
[urls]
这个分段及其它选项将在Web章节中进行讲解.
posted on 2016-08-18 17:32 胡小军 阅读(2265) 评论(0)  编辑  收藏 所属分类: Apache Shiro

只有注册用户登录后才能发表评论。


网站导航: