Apache Shiro在Web中的应用
概述
Shiro 是一个 Apache Incubator 项目,旨在简化身份验证和授权。本文只是我对shiro的初步认识,有不对的请大虾指正,谢谢!
?
基本概念
在对系统进行安全保障时,有两个安全性元素非常重要:身份验证和授权。虽然这两个术语代表的是不同的含义,但出于它们在应用程序安全性方面各自的角色考虑,它们有时会被交换使用。
身份验证 (我们平常接触较多的就是登录)指的是验证用户的身份。在验证用户身份时,需要确认用户的身份的确如他们所声称的那样。在大多数应用程序中,身份验证是通过用户名和密码的组合完成的。只要用户选择了他人很难猜到的密码,那么用户名和密码的组合通常就足以确立身份。但是,还有其他的身份验证方式可用,比如指纹、证书和生成键。
一旦身份验证过程成功地建立起身份,授权 (我理解的就是权限管理与控制)就会接管以便进行访问的限制或允许。所以,有这样的可能性:用户虽然通过了身份验证可以登录到一个系统,但是未经过授权,不准做任何事情。还有一种可能是用户虽然具有了某种程度的授权,却并未经过身份验证。
在为应用程序规划安全性模型时,必须处理好这两个元素以确保系统具有足够的安全性。身份验证是应用程序常见的问题(特别是在只有用户和密码组合的情况下),所以让框架来处理这项工作是一个很好的做法。合理的框架可提供经过测试和维护的优势,让您可以集中精力处理业务问题,而不是解决其解决方案已经实现的问题。
Apache Shiro 提供了一个可用的安全性框架,各种客户机都可将这个框架应用于它们的应用程序。本文中的这些例子旨在介绍 Shiro 并着重展示对用户进行身份验证的基本任务。
?
初识shiro
Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权。使用 Shiro,您就能够为您的应用程序提供安全性而又无需从头编写所有代码。
由于 Shiro 提供具有诸多不同数据源的身份验证,以及 Enterprise Session Management,所以是实现单点登录(SSO)的理想之选 ― 大型企业内的一个理想特性,因为在大型企业内,用户需要在一天内经常登录到并使用不同系统。这些数据源包括 JDBC、LDAP、 Kerberos 和 Microsoft? Active Directory? Directory Services (AD DS)。
Shiro 的 Session
对象允许无需 HttpSession
即可使用一个用户会话。通过使用一个通用的Session
对象,即便该代码没有在一个 Web 应用程序中运行,仍可以使用相同的代码。没有对应用服务器或 Web 应用服务器会话管理的依赖,您甚至可以在命令行环境中使用 Shiro。换言之,使用 Shiro 的 API 编写的代码让您可以构建连接到 LDAP 服务器的命令行应用程序并且与 web 应用程序内用来访问 LDAP 服务器的代码相同。
?
Shiro在Web中的应用
将shiro整合到Web中最简单的方式就是在web.xml文件中配置一个Servlet ContextListener的监听器和Filter过滤器。实例代码如下:
02 |
???? < listener-class >org.apache.shiro.web.env.EnvironmentLoaderListener</ listener-class >
|
08 |
???? < filter-name >ShiroFilter</ filter-name >
|
09 |
???? < filter-class >org.apache.shiro.web.servlet.ShiroFilter</ filter-class >
|
13 |
???? < filter-name >ShiroFilter</ filter-name >
|
14 |
???? < url-pattern >/*</ url-pattern >
|
以上的配置是假定shiro.ini的配置文件是放在以下两个位置下的(哪个先找到就用哪个):
?
1.?/WEB-INF/shiro.ini
2. classpath的根目录
以上的代码做了如下的工作:
-
EnvironmentLoaderListener初始化一个Shiro用的WebEnvironment实例(这个实例包括Shiro运行要用的任何东西,包含SecurityManager)并且让ServletContext可以访问它。可以在任何时刻调用WebUtils.getRequiredWebEnvironment(servletContext)语句获取WebEnvironment。
-
ShiroFilter将用WebEnvironment为所有的经过滤的request执行所有的安全保障操作。
-
最后,filter-mapping用以确保所有的请求通过ShiroFilter过滤。
注:建议将ShiroFilter filter-mapping声明在其他的filter-mapping声明之前,以确保Shiro也作用于那些filter。
?
Web INI 配置
默认情况下,初始化Shiro时,Shiro会自动按顺序搜索/WEB-INF/shiro.ini和classpath:shiro.ini位置下的.ini配置,然后用最先找到的那个。
如果配置较少,可以不用另外的.ini文件,而将INI配置在web.xml中。下面的配置就是将shiro的配置配置在web.xml中:
02 |
???? < listener-class >org.apache.shiro.web.env.EnvironmentLoaderListener</ listener-class >
|
08 |
???? < filter-name >ShiroFilter</ filter-name >
|
09 |
???? < filter-class >org.apache.shiro.web.servlet.ShiroFilter</ filter-class >
|
11 |
??????????? < param-name >config</ param-name >
|
12 |
??????????? < param-value >
|
13 |
???????????????? [users]
|
14 |
???????????????? # format: username = password, role1, role2, ..., roleN
|
15 |
???????????????? User1 = 123456
|
16 |
???????????????? Manager1 = 123456, Manager
|
17 |
???????????????? [filters]
|
18 |
???????????????? [urls]?
|
19 |
???????????????? /* = authc
|
20 |
??????????? </ param-value >
|
25 |
???? < filter-name >ShiroFilter</ filter-name >
|
26 |
???? < url-pattern >/*</ url-pattern >
|
27 |
</ filter-mapping ></ TT >
|
Web INI配置标准的有[main],[users],[roles],[urls]部分(具体如何配置可查看官方文档),下面是一个连接MySql数据库的配置示例:
02 |
ds = com.mysql.jdbc.jdbc2.optional.MysqlDataSource |
03 |
ds.serverName = 127.0 . 0.1
|
06 |
ds.databaseName = test |
08 |
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm |
09 |
jdbcRealm.permissionsLookupEnabled = true
|
10 |
jdbcRealm.authenticationQuery = SELECT password FROM ho_user WHERE name = ? |
11 |
jdbcRealm.userRolesQuery = SELECT role FROM ho_user WHERE name = ? |
12 |
jdbcRealm.permissionsQuery = SELECT permission FROM ho_user WHERE name = ? |
13 |
jdbcRealm.dataSource = $ds |
15 |
authc.loginUrl = /common/login.jsp |
16 |
perms.unauthorizedUrl = /common/login.jsp |
17 |
roles.unauthorizedUrl = /common/login.jsp |
21 |
/admin/**=authc,perms[high] |
22 |
/system/**=authc,perms[high] |
?
说明:
- jdbcRealm.dataSource = $ds指定jdbcRealm的数据源是前面配置的数据库ds。
- jdbcRealm.authenticationQuery,jdbcRealm.userRolesQuery,jdbcRealm.permissionsQuery 配置行是jdbc的与查询语句,它告诉shiro从何处获取授权的配置。它们都是用查询后的记录的第一个字段进行验证的,authenticationQuery是查询后取第一条记录的第一个字段(这里是password字段)进行验证;userRolesQuery是用查询后的第一个字段(这里是role字段)作为所属的角色的(可以有多条记录即多个角色);permissionsQuery同userRolesQuery一样查询后可以有多条记录,但也是取第一个字段作为权限字符串。
- authc.loginUrl = /common/login.jsp是指定如果验证失败则页面跳转到/common/login.jsp下,perms.unauthorizedUrl = /common/login.jsproles.unauthorizedUrl = /common/login.jsp同理。
- [urls] 里的配置就是对特定的url进行授权。/admin/**=authc,perms[high],是对匹配/admin/**的url配置权限,进入此 url须通过authc和perms[high]验证(authc和perms都是系统内置的过滤器。authc告诉shiro,进入此url,必须是已验证的登录用户;perms[high] 是权限限定符,perms是内置的过滤器,high是通过jdbcRealm.permissionsQuery查询出来的权限字符串,只有用户拥有该字符串的权限,才能获得访问授权。如果针对角色授权,可以是/admin/**=authc,roles[admin])。
附内置过滤器:
过滤器名
|
类
|
anon
|
org.apache.shiro.web.filter.authc.AnonymousFilter
|
authc
|
org.apache.shiro.web.filter.authc.FormAuthenticationFilter
|
authcBasic
|
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
|
logout
|
org.apache.shiro.web.filter.authc.LogoutFilter
|
noSessionCreation
|
org.apache.shiro.web.filter.session.NoSessionCreationFilter
|
perms
|
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
|
port
|
org.apache.shiro.web.filter.authz.PortFilter
|
rest
|
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
|
roles
|
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
|
ssl
|
org.apache.shiro.web.filter.authz.SslFilter
|
user
|
org.apache.shiro.web.filter.authc.UserFilter
|
?
Shiro应用
配置好Web INI后,就能将其应用在Web中了,我们可以看看有了Shiro后,安全验证时多么的简便。
?
假设有一个LoginAction,只需三句语句就能实现验证
01 |
public String execute() throws Exception {
|
03 |
???????????? AuthenticationToken token = new UsernamePasswordToken(username,password);
|
04 |
?????????????? Subject currentUser = SecurityUtils.getSubject();
|
05 |
???????????? currentUser.login(token);
|
06 |
???????????? return SUCCESS;
|
07 |
???? } catch (Exeception e){
|
08 |
???????????? Return ERROR;
|
只需两句话就能实现LogoutAction的动作
1 |
public String execute() throws Exception {
|
2 |
???????????? Subject currentUser = SecurityUtils.getSubject();
|
3 |
???????????? currentUser.logout();
|
4 |
???????????? return SUCCESS;
|
?
注:SecurityUtils
对象是一个 singleton,这意味着不同的对象可以使用它来获得对当前用户的访问。一旦成功地设置了这个SecurityManager
,就可以在应用程序不同部分调用SecurityUtils.getSubject()
来获得当前用户的信息。
?
补充说明:
上述代码中用到了Subject和UsernamePasswordToken
。这里增加一点
Shiro
的概念。
- Subject 是安全领域术语,除了代表人,它还可以是应用。在单应用中,可将其视为 User 的同义词。
- Principal 是 Subject 的标识,一般情况下是唯一标识,比如用户名。
- 用户令牌。在 Shiro 术语中,令牌 指的是一个键,可用它登录到一个系统。最基本和常用的令牌是
UsernamePasswordToken
,用以指定用户的用户名和密码。UsernamePasswordToken
类实现了AuthenticationToken
接口,它提供了一种获得凭证和用户的主体(帐户身份)的方式。UsernamePasswordToken
适用于大多数应用程序,并且您还可以在需要的时候扩展AuthenticationToken
接口来将您自己获得凭证的方式包括进来。例如,可以扩展这个接口来提供您应用程序用来验证用户身份的一个关键文件的内容。
更多的认证用法,请参考官方文档。
更多的授权用法,请参考官方文档。
?
JSP/GSP标签库
Shiro提供了JSP/GSP的标签库,这使得我们很容易能够在JSP,JSTL或GSP页面的控制基于Subject的状态的输出。
?
标签库的描述符(Tag Library Descriptor (TLD))在shiro-web.jar 的META-INF/shiro.tld下定义。要引用这些标签,只需在JSP页面的头部添加下面的语句:
1 |
< TT ><%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %></ TT >
|
如定义一个pag_header.jsp如下:
01 |
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> |
02 |
< div id = "page_header" >
|
03 |
?????? < div id = "page_heading" >Hello World</ div >
|
04 |
?????? < div id = "page_headerlinks" >
|
06 |
?????????????????? < li >
|
07 |
?????????????????? < shiro:guest >?
|
08 |
?????????????????? < a href = "/common/login.jsp" >Login Now</ a >
|
09 |
?????????????????? </ shiro:guest >
|
10 |
?????????????????? < shiro:user >
|
11 |
?????????????????? Welcome, < shiro:principal />
|
12 |
??????????????????? </ shiro:user >
|
13 |
?????????????????? </ li >
|
14 |
?????????????????? < script >
|
15 |
???????????????????????? var username = '< shiro:principal />';
|
16 |
?????????????????? </ script >
|
17 |
?????????????????? < li >
|
18 |
?????????????????? < shiro:guest >?
|
19 |
?????????????????? < a href = "/common/register.jsp" >Register Now</ a >
|
20 |
?????????????????? </ shiro:guest >
|
21 |
?????????????????? < shiro:user >
|
22 |
??????????????????????????????? < a href = "Logout.action" >Log out</ a >
|
23 |
?????????????????? </ shiro:user >
|
24 |
?????????????????? </ li >
|
27 |
?????? < div class = "clearthis" > </ div >
|
?
说明:guest标签只用于显示当前Subject被认为是“guest”的Subject内容。通常用于显示没有登录的内容。user标签只用于显示当前Subject被认为是“user”的Subject内容。通常用于显示已经登录的内容。一般情况下,两者是互斥的,只显其一。
更多标签用法,参见官方文档。