Java开源安全框架之Apache Shiro
APACHE SHIRO安全框架
1 背景
Shiro项目始于2003年初,当时它叫JSecurity项目,当时对于Java应用开发人员没有太多的安全替代方案,始终被一个叫JAAS(Java认证/授权服务)束缚着,但是JAAS缺点太多了,如它的授权机制太拙劣,用起来让人沮丧,又一方面JAAS跟虚拟机层面的安全问题关系非常紧密,如判断JVM中判断是否允许装入一个类等,还有加密问题,JAVA中的密码架构又是让人难以理解。于是Jsecurity就诞生了,后来更名为Shiro。
直到2008年Shiro加入到了APACHE软件基金会,直到现在它叫Apache Shiro。
2 特性
Ø 易于使用 :易用性是这个项目的最终目标。
Ø 广泛性:没有其他安全框架可以达到Shiro宣传的广度,它可以为你的安全需求提供“一站式”服务。
Ø 灵活性:Shiro可以工作在任何应用环境中,而不依赖于任何的应用环境。
Ø Web能力:Shiro对Web应用支持很神奇,允许你基于URL和WEB协议创建灵活的安全策略,同时还提供了一套控制页面输出的JSP标签库。
Ø 可插拔:Shiro干净的API和设计模式可以方便的与许多的其它框架和应用进行无缝集成。
Ø 支持:Shiro是APACHE软件基金会的成员。
3 简介
Apache Shiro是一个强大易用的Java安全框架,可以用其为你的应用护航。通过简化应用安全的四个领域,即认证、授权、会话管理和加密,在真实应用中,应用安全能更容易被理解和实现。
Shiro的简单架构和兼容JavaBean几乎能够在任何环境下配置和使用。附加的Web支持和辅助功能,比如多线程和测试支持,让这个框架为应用安全提供了“一站式”服务。Apache Shiro开发团队将继续前进,精炼代码库和支持社区。随着持续被开源和商业应用采纳,可以预期Shiro会继续发展壮大。
(1)认证:用户身份识别,常被称之为“登陆”操作。
(2)授权:访问控制。
(3)加密:密码加密,保护数据以防被偷窥。
(4)会话:会话管理,每用户相关的时间敏感的状态。
4 SHIRO重要组件
Shiro组件图形 1 重要组件
一. 主要的特性:
1.Authentication: 认证(也就是登陆),这是一个证明用户所说的他们是谁的行为。
2.Authorization: 授权(访问控制的过程),也就是说“谁”去访问了什么。
3.Session Manager: 管理用户特定的会话,即使在非Web或EJB的应用程序中。
4.Cryptography: 通过使用加密算法保护数据安全同时易于使用。
二. 支持的特性:
1.Web Support: Web支持:Shiro Web API能够轻松的帮助保护Web应用程序。
2.Caching: 缓存支持:可以让安全操作快速而高效。
3.Concurrency: 多线程支持:Apache Shiro利用它的并发特性来支持多线程应用程序。
4.Testing: 测试支持:帮助你编写单元测试和集成测试,确保你的应用能够按如期一样安全。
5.“Run As”: 一个允许用户假设为另一个用户身份的功能。
6.“Remember Me”: 在会话中记住用户身份,所以我们只需要在强制的时候登陆。
5 SHIRO核心组件
Shiro组件图形 2 核心组件
1.Subject:
即当前操作用户,但是在Shiro中,Subject这一概念并不仅仅指人,也考虑到第三方进程、后台账户或者其他类似事物,它仅仅意味着“当前跟软件交互的东西”,你可以把它认为是Shiro的“用户”概念。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
2.SecurityManager:
它是Shiro框架的核心,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
特性:
Ø 基于POJO实现
Ø 简单的客户端存储
Ø 独立的容易集成
Ø 异构的客户端访问
Ø 事件的监听
Ø 主机地址的保留
Ø 不活动/过期的支持
Ø 网络支持
Ø 支持SSO
3.Realm:
Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当用户执行认证、授权的时候,Shiro会从应用配置的Realm中查找用户及权限信息。在配置Shiro时,你必须至少指定一个Realm用于认证和授权,配置多个Realm是没有问题的,但是至少需要配置一个。Realm内置了大量的安全的数据源,如:JDBC(关系型数据库)的Realm,LDAP(目录)的Realm,INI文本配置资源的Realm等,如果默认的Realm不能满足要求,还可以插入代表自定义数据源的Realm实现。
6 SHIRO架构图
Shiro组件图形 3 架构图
7 SHIRO认证过程
Shiro组件图形 4 认证过程
第一步:
应用程序构建一个终端用户认证信息的AuthenticationToken实例后,调用Subject.login()方法。
第二步:
Subject的实例通常是DelegaingSubject类或它的子类实例对象,在认证时,会委托应用程序设置securityManager实例然后调用securityManager.login(token)方法。
第三步:
SecurityManager接收到token信息后会委托内置的Authenticator实例(通常
都是ModularRealmAuthenticator类的实例)调用authenticator.authenticate(token).
ModularRealmAuthenticator在认证过程中会对设置的一个或多个Realm实例进行适配,它实际上为Shiro提供一个可插拔的认证机制。
第四步:
如果在应用程序中配置多个Realm,ModularRealmAuthenticator会根据配置AuthenticationStrategy(认证策略)来进行多Realm的认证过程。在Realm被调用后,AuthenticationStrategy将对每一个Realm的结果做出相应。(如果应用程序中仅配置一个Realm,Realm将被直接调用无需再配置认证策略)
第五步:
判断每一个Realm是否支持提交的Token,如果支持,Realm将调用getAuthenticationInfo(token),这个方法就是实际认证处理,我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们自定义的认证处理。
7.1 记住我和已认证
1.Shiro的RememberMe 和 通过认证的Authenticated有明确的区分:
(1).Remembered(记住我):
一个已记住的Subject不是匿名的,而且有一个已知的身份ID(也就是调用subject.getPrincipals()方法是非空的),但是这个被记住的身份ID是在之前的Session中被认证的,如果调用subject.isRemembered()返回true,则Subject被认为是被记住的。
(2).Authenticated(已认证):
一个已认证的Subject是指在当前Session中被成功地验证过了(也就是说subject.login()方法被调用而且没有抛出异常),如果调用subject.isAuthenticated()返回true则认为Subject已通过验证。
注意:Remembered和Authenticated是互斥的,其中一个为真则另一个为假,反之亦然。
7.2 令牌
1. 令牌是一个键,可以用它登陆一个系统,常用令牌是UsernamePasswordToken,用它可以指定用户名和密码。
2. UsernamePasswordToken类适用于大多数应用程序,可以扩展这个接口来提供您应用程序用来验证身份的一个关键文件内容。
3. 如果用户密码不正确会抛出:IncorrectCredentialsException,在生产代码中显示处理;
如果用户账户不正确会抛出:UnknownAccountException,在生产代码中显示处理;
如果用户账户被锁定会抛出:LockedAccountException,在生产代码中显示处理;
登陆失败时其它异常会抛出:AuthenticationException,在生产代码中显示处理;
注意:既然能提供详细的用户名和密码验证信息,但是我们最好不要提供详细的提示,避免用户猜测来登陆系统。
7.3 认证策略
如果你的应用程序中使用多个Realm从多个数据源获取账户资料,AuthenticationStrategy是最终为最后的“合并”能够被应用程序理解的Subject的身份的视图负责人。
认证策略的类 |
描述 |
AtLeastOneSuccessfulStrategy |
如果一个或更多的Realm验证成功,则整体尝试被认为是成功的,如果没有一个验证成功,则整体尝试失败。 |
FirstSuccessfulStrategy |
只有第一个成功的验证Realm返回的信息将被使用,所有进一步的Realm将被忽略,如果没有一个验证成功,则整体尝试失败。 |
AllSuccessfulStrategy |
为了整体的尝试成功,所有配置的Realm必须验证成功,如果没有一个验证成功,则整体尝试失败。 |
7.4 注销
注销就是释放所有已知的识别状态,调用subject.logout()方法来释放识别状态信息。
调用logout会发生:
(1). 现有的Session将会失效,任何的身份将失去关联。
(2). RememberMe cookie也将被删除。
注意:由于Web应用程序依靠Cookies来记住用户身份,然后Cookies只能在Response被committed之前被删除,所以建议在调用注销方法之后将终端用户重定向到一个新的视图或页面,这样能够保证任何的安全相关的Cookies都能想预期一样被删除,这个是http cookies的功能,不是Shiro的。
8 SHIRO授权过程
Shiro组件图形 5 授权过程
第一步:
在应用程序中调用授权验证方法(例如:Subject的isPermitted*或者hasRole*等等)。
第二步:
Subject实例通常是DelegatingSubject的类或者它子类的实例对象,在认证开始时,会委托应用程序设置securityManager实例调用相应的 isPermitted*或者hasRole*方法。
第三步:
接下来SecurityManager会委托内置的Authorizer实例(默认是
ModularRealmAuthorizer类的实例,它支持一个或者多个Realm实例认证)调用相应的授权方法。
第四步:
每一个Realm将检查是否实现了相同的Authorizer接口,然后,将调用Realm自己相应的授权验证方法。
8.1 权限粒度
Shiro权限声明可以做到非常细粒度,权限声明通常是使用以冒号分隔的表达式,一个权限表达式可以清晰的指定资源类型、允许的操作、可访问的数据。同时Shiro权限表达式支持简单的通配符,可以更加灵活的进行权限设置。
8.2 角色模式
Shiro的角色模式有传统角色、权限角色两种角色模式。
(1). 隐式角色(旧RBAC):当需要对某一操作进行授权时,只需要判断是否拥有该角色即可,这种角色权限相对简单、模糊。
(2). 显示角色(新RBAC):一个角色拥有一个权限集合,授权时,需要判断当前角色是否拥有该权限,这种角色权限可以对用户详细的权限描述。
8.3 授权实现方式
Shiro支持三种授权方式,即编码方式、注解方式、自定义标签方式。
8.3.1 编码实现
8.3.1.1Subject API【常用】
类型 |
方法和描述 |
void |
checkPermission 检查是否拥有指定的权限对象 |
void |
checkPermission 检查是否拥有指定的权限字符串 |
checkPermissions |
|
void |
checkPermissions 检查是否拥有指定的所有权限字符串 |
void |
checkRole 断言Subject拥有指定的角色 |
void |
checkRoles 断言Subject拥有集合的所有角色 |
void |
checkRoles 断言Subject拥有指定全部字符数组权限 |
Object |
返回应用程序范围的Subject唯一当事人标识,如果为NULL说明Subject是匿名的。 |
Session |
返回与Subject相关的应用程序会话 |
boolean |
hasAllRoles 如果Subject拥有指定的集合角色,返回True否则false |
boolean |
hasRole 如果Subject拥有指定字符串角色,返回Treu,否则false |
Boolean[] |
hasRoles 检查Subject是否拥有指定的角色,返回一个布尔型数组。 |
boolean |
是否被认证过,返回True,如果Subject/User证明了自己的身份在当前会话中 |
boolean[] |
isPermitted 检查是否拥有指定的权限集合,返回一个布尔值数组。 |
boolean |
isPermitted 如果Subject拥有指定的权限,返回True,否则返回false |
boolean |
isPermitted 检查是否拥有指定权限数组,返回一个布尔型数组。 |
boolean |
isPermitted 如果Subject拥有指定权限字符串权限,返回True,否则返回false |
boolean |
isPermittedAll 如果Subject拥有指定集合的所有权限,返回True,否则返回False |
boolean |
isPermittedAll 如果Subject拥有指定字符数组的所有权限,返回True,否则返回False |
boolean |
如果Subject不是匿名的,返回True,否则返回False |
void |
login 执行Subject的登陆操作 |
void |
logout 退出登陆,移除Subject相关认证和授权数据 |
8.3.2 注解实现
Shiro注解支持AspectJ、Spring、Google-Guice等,可根据应用进行不同的配置,注解可用于类/属性/方法。
8.3.2.1@ RequiresAuthentication
用于表明访问该资源的用户需是经过认证。
8.3.2.2@ RequiresGuest
表明访问该资源的用户为Guest用户。
8.3.2.3@ RequiresPermissions
当前用户需拥有指定权限。
8.3.2.4@ RequiresRoles
当前用户需拥有指定角色。
8.3.2.5@ RequiresUser
当前用户需为已认证用户或已记住用户。
8.3.3 标签实现
8.3.3.1JspTaglib实现
Shiro提供了一套JSP标签来实现页面授权访问控制:在使用Shiro标签库前,首先需要在JSP引入shiro标签:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> |
8.3.3.2guest标签
验证当前用户是否为“访客”,即未认证(包含未记住)的用户
<shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a> today! </shiro:guest> |
8.3.3.3user标签
认证通过或已记住的用户
<shiro:user> Welcome back John! Not John? Click <a href="login.jsp">here<a> to login. </shiro:user> |
8.3.3.4authenticated标签
已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。
<shiro:authenticated> <a href="updateAccount.jsp">Update your contact information</a>. </shiro:authenticated> |
8.3.3.5notAuthenticated标签
未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
<shiro:notAuthenticated> Please <a href="login.jsp">login</a> in order to update your credit card information. </shiro:notAuthenticated> |
8.3.3.6principal标签
输出当前用户信息,通常为登录帐号信息。
Hello, <shiro:principal/>, how are you today? |
8.3.3.7hasRole标签
验证当前用户是否属于该角色
<shiro:hasRole name="administrator"> <a href="admin.jsp">Administer the system</a> </shiro:hasRole> |
8.3.3.8lacksRole标签
与hasRole标签逻辑相反,当用户不属于该角色时验证通过
<shiro:lacksRole name="administrator"> Sorry, you are not allowed to administer the system. </shiro:lacksRole> |
8.3.3.9hasAnyRole标签
验证当前用户是否属于以下任意一个角色。
<shiro:hasAnyRoles name="developer, project manager, administrator"> You are either a developer, project manager, or administrator. </shiro:lacksRole> |
8.3.3.10 hasPermission标签
验证当前用户是否拥有指定权限。
<shiro:hasPermission name="user:create"> <a href="createUser.jsp">Create a new User</a> </shiro:hasPermission> |
8.3.3.11 lacksPermission标签
与hasPermission标签逻辑相反,当前用户没有指定权限时,验证通过 。
<shiro:hasPermission name="user:create"> <a href="createUser.jsp">Create a new User</a> </shiro:hasPermission> |
9 术语
9.1 Authentication
身份验证是验证Subject 身份的过程——实质上是证明某些人是否真的是他们所说的他们是谁。当认证尝试成功后,应用程序能够相信该subject 被保证是其所期望的。
9.2 Authorization
授权,又称为访问控制,是决定一个user/Subject 是否被允许做某事的过程。它通常是通过检查和解释Subject的角色和权限(见下文),然后允许或拒绝到一个请求的资源或功能来完成的。
9.3 Cipher['saɪfə]
密码是进行加密或解密的一种算法。该算法一般依赖于一块被称为key 的信息。基于不同的key 的加密算法也是不一样的,所以解密没有它是非常困难的。密码有不同的表现形式。分组密码致力于符号块,通常是固定大小的,而流密码致力于连续的符号流。对称性密码加密和解密使用相同的密钥(key),而非对称性加密使用不同的密钥。如果非对称性加密的密钥不能从其他地方得到,那么可以创建公钥/私钥对公开共享。
9.4 Credential[krɪ'denʃ(ə)l]
凭证是一块信息,用来验证user/Subject 的身份。在认证尝试期间,一个(或多个)凭证与Principals(s)被一同提交,来验证user/Subject 所提交的确实是所关联的用户。证书通常是非常秘密的东西,只有特定的user/Subject 才知道,如密码或PGP 密钥或生物属性或类似的机制。这个想法是为principal 设置的,只有一个人会知道正确的证书来“匹配”该principal。如果当前user/Subject
提供了正确的凭证匹配了存储在系统中的,那么系统可以假定并信任当前user/Subject 是真的他们所说的他们是谁。信任度随着更安全的凭证类型加深(如,生物识别签名 > 密码)。
9.5 Cryptography[krɪp'tɒgrəfɪ]
加密是保护信息不受不希望的访问的习惯做法,通过隐藏信息或将它转化成无意义的东西,这样没人可以理解它。Shiro 致力于加密的两个核心要素:加密数据的密码,如使用公钥或私钥的邮件,以及散列表(也称消息摘要),它对数据进行不可逆的加密,如密码。
9.6 Hash
散列函数是单向的,不可逆转的输入源,有时也被称为消息,在一个编码的哈希值内部,有时也被称为消息摘要。它通常用于密码,数字指纹,或以字节数组为基础的数据。
9.7 Permission
权限,至少按照Shiro 的解释,是在应用程序中描述原始功能的一份声明并没有更多的功能。权限是在安全策略中最低级别的概念。它们仅定义了应用程序能够做“什么”。它们没有说明“谁”能够执行这些操作。权限只是行为的声明,仅此而已。
一些权限的例子:
打开文件
浏览'/user/list'页面
打印文档
删除'jsmith'用户
9.8 Principal ['prɪnsəp(ə)l]
Principal 是一个应用程序用户(Subject)的任何标志属性。“标志属性”可以是任何对你应用程序有意义的东西——用户名,姓,名,社会安全号码,用户ID 等。这就是它——没什么古怪的。Shiro 也引用一些我们称之为Subject 的primary principal 的东西。一个primary principal 是在整个应用程序中唯一标识Subject 的principal。理想的primary principal 是用户名或RDBMS 用户表主键——用户ID。对于在应用程序中的用户(Subject)来说,只有一个primary principal
9.9 Realm
Realm 是一个能够访问应用程序特定的安全数据(如用户,角色和权限)的组件。它可以被看作是一个特定安全的DAO(Data Access Object)。Realm 将这些应用程序特定的数据转换成Shiro 能够理解的格式,这样Shiro反过来能够提供一个单一的易于理解的Subject 编程API,无论有多少数据源存在或无论你的数据是什么样的应用程序特定的格式。Realm 通常和数据源是一对一的对应关系,如关系数据库,LDAP 目录,文件系统,或其他类似资源。因此,Realm 接口的实现使用数据源特定的API 来展示授权数据(角色,权限等),如JDBC,文件IO,Hibernate 或JPA,或其他数据访问API。
9.10 Role
基于你对话的对象,一个角色的定义是可以多变的。在许多应用程序中,它充其量是个模糊不清的概念,人们用它来隐式定义安全策略。Shiro 偏向于把角色简单地解释为一组命名的权限的集合。这就是它——一个应用程序的唯一名称,聚集一个或多个权限声明。这是一个比许多应用程序使用的隐式的定义更为具体的定义。如果你选择了你的数据模型反映Shiro 的假设,你会发现将有更多控制安全策略的权力。
9.11 Session
会话是一个在一段时间内有状态的数据,其上下文与一个单一的与软件系统交互的user/Subject 相关联。当Subject 使用应用程序时,能够从会话中添加/读取/删除数据,并且应用程序稍后能够在需要的地方使用该数据。会话会被终止,由于user/Subject 注销或会话不活动而超时。对于那些熟悉HttpSession 的,Shiro Session 服务于同一目标,除了Shiro 会话能够在任何环境下使用,甚至在没有Servlet 容器或EJB 容器的环境。
9.12 Subject
Subject 只是一个精挑细选的安全术语,基本上的意思是一个应用程序用户的安全特定的“视图”。然而Subject不总是需要反映为一个人——它可以代表一个调用你应用程序的外部进程,或许是一个系统帐户的守护进程,在一段时间内执行一些间歇性的东西(如一个cron job)。它基本上是任何使用应用程序做某事的实体的一个代表。
10 问题列表
10.1 如何实现无刷新登陆验证提示?
http://www.kankanews.com/ICkengine/archives/48199.shtml
上面这个地址里面有详细介绍………
10.2 Realm是什么?
Realm有iniRealm、jdbcRealm、ldapRealm等,在认证、授权内部实现机制中都有提到,最终处理都将交给Realm进行处理,因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色、权限的,通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的认证信息,可以说,Realm是专用于安全框架的Dao.
10.3 两个Session的区别?
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
Session.setAttribute(“someKey”,someValue);
HttpServletRequest.getSession(Boolean create)和Subject.getSession(Booleancreate)方法有异曲同工之效。
1. 如果Subject已经拥有了一个Session,则create参数被忽略且Session被立即返回。
2. 如果Subject还没有一个Session,且create参数为true,则创建一个新的会话并返回该会话。
3. 如果Subject还没有一个Session,且create参数为false,则不会创建新的会话且返回null。
10.4 URL过滤器如何配置?
Shiro主要是通过URL过滤来进行安全管理。
Shiro可以通过配置文件实现基于URL的授权验证。
每个URL配置,表示匹配该URL的应用程序请求将由对应的过滤器进行验证。
URL目录是基于HttpServletRequest.getContextPath()此目录设置。
URL可使用通配符,**代表任意子目录
Shiro验证URL时,URL匹配成功便不在继续匹配查找,所以要注意配置文件中的URL顺序,尤其在使用通配符时。
一个URL可以配置多个Filter,使用逗号分隔。
当设置多个过滤器时,全部验证通过,才视为通过。
部分过滤器可指定参数,如perms, roles。
3. 拦截器类型:
过滤器名 |
过滤器说明 |
过滤器类 |
anon |
匿名过滤器 |
|
anthc |
如果继续操作,需要做对应的表单验证,否则不能通过 |
|
authcBasic |
基于http验证过滤,如果不通过,跳转到登陆页面 |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
logout |
登录退出过滤器 |
|
noSessionCreation |
没有Session创建过滤器 |
|
perms |
权限过滤器 |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
端口过滤器,可以设置是否是指定端口,如果不是跳转到登陆页面 |
|
rest |
http方法过滤器,可以指定如post不能进行访问等 |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
角色过滤器,判断当前用户是否指定角色 |
|
ssl |
请求需要通过ssl,如果不是跳转到登陆页 |
|
user |
如果访问一个已知用户,比如记住我的功能,走这个过滤器 |
10.5 记住我和已认证什么时候用?
这一般是应用于电子商务网站中,举个例子:比如你正在访问亚马逊,你弄了几本书放到了购物车中,当你正要结账的时候,你突然有些事情不得不出去一下,结果你回来时候已经到了下班时间了,结果你就关机下班回家了,当你到家的时候你突然想起来还有几本书没有完成付款,结果你打开网站要继续完成支付时你发现网站中还记录着你是谁,你看到了欢迎页面,但是当你要确认支付访问你的信用卡账户信息时,网站会强制让你再次输入密码,已达到已认证的状态,因为亚马逊网站只记得你是谁,但是那并不能证明你就是你,也可能是你的同事或者陌生人在使用你的电脑。
记住我和已认证一定是互斥的,一个为真则另一个肯定为假!