最新版Apache Shiro 的Web应用支持指南

为了学习和阅读的方便,本篇内容,主要介绍包括Shiro的web.xml配置、默认过滤器、启用与禁止过滤器、会话管理、记住我服务以及标签库六大部分,以便全面掌握Apache Shiro这个Java安全管理框架的web应用支持相关内容。内容有点多,可以花点时间仔细看,或收藏后闲暇时再研究。 其它相关概念和基础原理内容,可以参考已发布的“Shiro入门系列”文章。

1. 配置

将Shiro集成到任何Web应用程序中的最简单方法是在web.xml中配置Servlet ContextListener和Filter,它们了解如何读取Shiro的INI配置。 大部分的INI配置格式本身是在配置页面的INI部分中定义的,但我们将在这里介绍一些其他特定于网络的部分。

提示:Spring Framework用户不要执行此配置。 如果你使用Spring,我们另行讲解特定于Spring的web配置。

1.1 web.xml文件配置

1. Shiro1.2及更高版

在Shiro 1.2及更高版本中,标准Web应用程序通过将以下XML块添加到web.xml来初始化Shiro:

web.xml配置截图

这种配置假设Shiro的INI配置文件位于以下两个位置中的任何一个,先找到那个就以哪个为准:

1)/WEB-INF/shiro.ini;

2)shiro.ini 文件在类路径的根目录下;.

上面的配置所完成的工作如下:

•EnvironmentLoaderListener初始化Shiro的WebEnvironment实例(它包含Shiro需要操作的所有内容,包括SecurityManager),并使其在ServletContext中可访问。 如果您需要随时获得此WebEnvironment实例,则可以通过调用WebUtils.getRequiredWebEnvironment(servletContext)获得。

•ShiroFilter将使用此WebEnvironment为任何已过滤的请求执行所有必要的安全操作。

•最后,过滤器映射的定义,确保所有请求都由ShiroFilter过滤,推荐大多数Web应用程序这样做,以确保任何请求受保护。

注意:ShiroFilter的shiro-mapping配置

通常希望在任何其他的"过滤器映射"声明之前定义"ShiroFilte的过滤器映射",以确保Shiro也可以在这些过滤器中运行。

定制WebEnvironment类?

默认情况下,Shiro的EnvironmentLoaderListener将基于INI的配置创建一个IniWebEnvironment实例。 如果你乐意,可通过在web.xml中指定一个ServletContext上下文参数来指定一个自定义WebEnvironment实例

自定义WebEnvironment

这使您可以自主定义配置格式并将其解析为WebEnvironment实例。 您可以将现有的IniWebEnvironment子类化,实现自定义行为,或完全支持不同的配置格式。 例如,如果有人想用XML而不是INI配置Shiro,他们可以创建一个基于XML的实现,例如, com.foo.bar.shiro.XmlWebEnvironment。

定制配置文件位置

IniWebEnvironment类期望读取和加载INI配置文件。 默认情况下,该类将自动在以下两个位置查找Shiro .ini配置(按顺序):

1) /WEB-INF/shiro.ini

2) classpath:shiro.ini

并且以首先找到的为准则。

然而,如果您希望将您的配置置于其他位置,则可以使用web.xml中的另一个上下文参数指定该位置:

自定义配置文件位置

默认情况下,param值预计可以通过ServletContext.getResource方法定义的规则解析。 例如,/WEB-INF/some/path/shiro.ini

但是,您也可以通过使用Shiro的ResourceUtils类支持的适当的资源前缀来指定特定的文件系统、类路径或URL位置,例如:

1)file:/home/foobar/myapp/shiro.ini

2)classpath:com/foo/bar/shiro.ini

3)url:http://confighost.mycompany.com/myapp/shiro.ini

1. Shiro1.1及更早版

在1.1或更早版本的Web应用程序中启用Shiro的最简单方法是定义IniShiroFilter并指定过滤器映射:

此定义假定您的INI配置位于类路径根目录的shiro.ini文件中(例如classpath:shiro.ini)。

定制路径

如果您不想将INI配置置于/WEB-INF/shiro.ini或classpath:shiro.ini中,则可以根据需要指定自定义资源位置。 添加configPath init-param并指定资源位置:

不合格(无模式或'非前缀')configPath值被假定为ServletContext资源路径,可通过ServletContext.getResource方法定义的规则解析。

注意:Shiro 1.2+ ServletContext资源路径问题

ServletContext资源路径在Shiro 1.2和更高版本中可用。 在1.1及更早版本中,所有的configPath定义都必须指定一个classpath、 file:或url:前缀。

您还可分别classpath:、url:或file:前缀来指示其他非ServletContext资源位置相应的类路径、url 或文件系统的位置。例如:

内联配置

最后,也可以将INI配置嵌入到web.xml中,而不需要使用INI文件。 您可以通过使用config init-param而不是configPath来完成此操作

对于小型应用程序或简单应用程序,内联配置通常很好,但由于以下原因,将其外部化在专用shiro.ini文件中通常更方便:

•可能会编辑很多安全配置,并且不希望将修订控制"噪音"添加到web.xml文件中;

•可能希望将安全配置从web.xml配置的其余部分中分离出来;

•安全配置可能会变大,您希望保持web.xml精简并易于阅读;

•有一个复杂的构建系统,可能需要在多个位置引用相同的shiro配置。

用哪一种配置,这取决于你——以对你的项目有意义(实际场景)为准。

1.2 Web INI配置

除了前面配置章节(查阅已发布的Shiro配置详解部分)中已经描述的标准[main], [users] 和[roles]部分之外,您还可以在shiro.ini文件中指定特定于Web的url部分:

[urls]部分允许您执行任何我们所见过的任何Web框架中不存在的功能:可以为应用程序中的任何匹配URL路径定义特别的过滤器链

这比你在web.xml中通常定义过滤器链的方式更灵活、更强大、更简洁:即使你从来没有使用过Shiro提供的任何其他特性,且仅使用这个功能(URL配置),它本身也会使其值得使用。

下面的内容,在“Shiro配置详解”篇中已经讲过,这里为了阅读的连续性,再次在此进行描述:

1) [urls]

[urls]部分中每行的格式如下:

_URL_Ant_Path_Expression_ = _Path_Specific_Filter_Chain_

示例如下:

接下来我们将阐述这些行的意思。

等号(=)左侧的标记是相对于Web应用程序的上下文根的Ant式路径表达式。

例如,假设您有以下[urls]行:

/account/** = ssl, authc

此行描述的是:"对我的应用程序/account路径或其任何子路径(/account/foo,/account/bar/baz等)的任何请求都会触发'ssl,authc'过滤器链"。 我们下面将描述过滤器链。

请注意,所有路径表达式都与您的应用程序的根上下文相关。 这意味着如果您有一天将应用程序部署到www.somehost.com/myapp,然后又将其部署到www.anotherhost.com(没有'myapp'子路径),则模式匹配仍然可以工作。 所有路径都与HttpServletRequest.getContextPath()值有关。

小心:顺序问题

URL路径表达式按照它们传入请求的定义顺序和“FIRST MATCH WINS”(首先匹配获胜)进行鉴定。 例如,让我们假设有以下定义链:

如果传入的请求打算到达/account/signup/index.html(可供所有'匿名'用户访问),它将永远不会被处理! 原因是/ account / **模式首先匹配传入请求,并将所有剩余定义"短路"。

永远记得根据FIRST MATCH WINS策略定义您的过滤器链!

2) 过滤链定义

等号(=)右侧的标记是用逗号分隔的过滤器列表,用于执行与该路径匹配的请求。 它必须符合以下格式:

filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN]

其中:

• filterN是[main]部分中定义的过滤器bean的名称,并且

• [optional_configN] 是一个可选的括号内的字符串,对那个特定路径的特定过滤器有意义(每个过滤器,特定于路径的配置!)。 如果过滤器不需要该URL路径的特定配置,则可以放弃括号,因此filterN[] 只会变成filterN。

并且因为过滤令牌定义了链(又名列表),请记住该顺序很重要! 按照您希望请求流经的顺序定义您的逗号分隔链列表。

最后,如果不满足其必要条件(例如,执行重定向,用HTTP错误代码响应,直接渲染等),每个过滤器都可以自由处理响应。 否则,按照预期,它会允许请求经由过滤链,到最终的目标视图。

提示:

能够对特定路径的配置作出反应,即过滤器令牌的[optional_configN]部分,是Shiro过滤器可用的独特功能。

如果你想创建自己的javax.servlet.Filter实现,也可以这样做,确保你的过滤器为org.apache.shiro.web.filter.PathMatchingFilter的子类。

3) 可用过滤器

可用于过滤器链定义的"过滤器"池在[main]部分定义。 在main部分分配给他们的名称是过滤器链定义中使用的名称。 例如:

2. 默认过滤器

运行Web应用程序时,Shiro将创建一些有用的默认过滤器实例,并自动在main部分使其可用。 您可以像在任何其他bean中一样配置它们,并在链定义中引用它们。 例如:

可用的默认Filter实例自动由枚举类DefaultFilter完成定义,枚举的name字段是可用于配置的名称。 他们是:

3. 启用禁止过滤器

与任何过滤器链定义机制(web.xml,Shiro的INI等)一样,只需将过滤器包含在过滤器链定义中即可启用该过滤器,并通过将其从链定义中删除来禁用过滤器。

但Shiro 1.2中新增的一项新功能是可以启用或禁用滤波器,而无需从滤波器链中删除滤波器。 如果启用(默认设置),则会按预期过滤请求。 如果禁用,则过滤器将允许请求立即通过FilterChain中的下一个元素。 通常可以基于配置属性触发过滤器的启用状态,或者甚至可以基于每个请求触发它。

这是一个强大的概念,因为根据某些要求启用或禁用过滤器通常比更改静态过滤器链定义更方便,而静态过滤器链定义是永久性且不灵活的。

Shiro通过它的OncePerRequestFilter抽象父类完成此操作。 所有Shiro的开箱即用滤波器实现都是这个子类的一个子类,因此可以在不从滤波器链中删除它们的情况下启用或禁用它们。 如果您也需要此功能,您可以将此类继承为您自己的过滤器实现*。

*SHIRO-224(https://issues.apache.org/jira/browse/SHIRO-224)将有希望为任何过滤器启用此功能,而不仅仅是那些子类OncePerRequestFilter。 如果这对你很重要,请为此问题投票。

3.1 通用启用/禁用

OncePerRequestFilter(及其所有子类)支持跨所有请求启用/禁用过滤器,与基于每个请求的一样。

通常为所有请求启用或禁用过滤器是通过将其enabled属性设置为true或false来完成的。 默认设置为true,因为大多数滤波器在链中配置时是固化需要执行的。

例如,在shiro.ini中:

这个例子表明,很多URL路径可能都需要一个请求必须通过SSL连接来保护。 在开发过程中设置SSL可能令人沮丧且费时。 在开发过程中,您可以禁用SSL过滤器。 部署到生产环境时,您可以使用一个配置属性启用它 - 这比手动更改所有URL路径或维护两个Shiro配置要容易得多。

3.2 特殊请求的启用和禁用

OncePerRequestFilter根据isEnabled(request,response)方法实际确定是启用还是禁用过滤器。

此方法默认返回enabled属性的值,该属性用于通常启用/禁用上述所有请求。 如果您想根据请求特定条件启用或禁用过滤器,则可以覆盖OncePerRequestFilter.isEnabled(request,response)方法以执行更具体的检查。

3.3 特殊路径的启用和禁用

Shiro的PathMatchingFilter(OncePerRequestFilter的一个子类)可以根据被过滤的特定路径对配置做出反应,这意味着除了传入的请求和响应之外,还可以根据路径和路径特定的配置启用或禁用过滤器。

如果您需要能够响应匹配路径和特定于路径的配置以确定过滤器是启用还是禁用,而不是重写OncePerRequestFilter.isEnabled(request,response)方法,则可以覆盖PathMatchingFilter.isEnabled(request,response ,path,pathConfig)方法。

4. 会话管理

4.1 Servlet容器会话

在Web环境中,Shiro的默认会话管理器SessionManager的实现是ServletContainerSessionManager类。 这个非常简单的实现将所有会话管理职责(包括会话群集,如果servlet容器支持它)委托给运行时Servlet容器。 它本质上是Shiro的会话API到servlet容器的桥梁,除此之外别无其他。

使用此默认值的好处是,应用与现有servlet容器的会话配置(超时、任何特定于容器的群集机制等)协同工作,且能按预期工作。

这个默认方式的缺点是把你绑定到servlet容器的特定会话行为。 例如,如果您想要群集会话,但是您使用Jetty进行测试并在生产中使用Tomcat,则您的容器特定配置(或代码)将不可移植。

Servlet容器会话超时

如果使用默认的servlet容器支持,则可以在Web应用程序的web.xml文件中按预期配置会话超时。 例如:

4.2 本地会话

如果您希望您的会话配置设置和集群可以跨servlet容器(例如,测试中的Jetty,生产中的Tomcat或JBoss)移植,或者您想要控制特定的会话/集群功能,则可以启用Shiro的本地会话管理。

这里"Native"一词意味着Shiro自己的企业会话管理实现可用于支持所有Subject主体和HttpServletRequest会话,并完全绕过Servlet容器。 但是请放心 - Shiro直接实现了Servlet规范的相关部分,因此任何现有的Web/http相关代码都能按预期工作,并且从不需要"知道"Shiro透明地管理着会话。

1)默认web会话管理器DefaultWebSessionManager

要为您的Web应用程序启用本地会话管理,您需要配置一个支持Web的本地会话管理器来覆盖默认的基于Servlet容器的会话管理器。 您可以通过在Shiro的SecurityManager上配置DefaultWebSessionManager的实例来实现这一点。 例如,在shiro.ini中(shiro.ini 中本地web会话管理器配置代码清单如下):

本地会话管理器配置清单

一旦声明,您可以使用会话超时和集群配置等本机会话选项配置DefaultWebSessionManager实例,如会话管理部分中所述。

2)本地会话超时

在配置DefaultWebSessionManager实例后,会话超时配置如“会话管理:会话超时”(http://shiro.apache.org/session-management.html#SessionManagement-sessionTimeout)部分所述一样(将另行讲解)。

3)会话cookie

DefaultWebSessionManager支持两种特定Web的配置属性:

• sessionIdCookieEnabled(一个布尔值)

• sessionIdCookie,一个Cookie实例。

注意:模板cookie——sessionIdCookie属性本质上是一个模板 - 您配置的Cookie实例属性,那么此模板将用于在运行时使用适当的会话ID值设置实际的HTTP"Cookie"头。

4)会话cookie配置

DefaultWebSessionManager的sessionIdCookie默认实例是一个SimpleCookie。 这个简单的实现,允许你希望在httpCookie上配置的所有相关属性,以JavaBeans风格的属性配置实现。

例如,您可以设置Cookie域:

有关其他属性,请参阅SimpleCookie JavaDoc(http://shiro.apache.org/static/current/apidocs/org/apache/shiro/web/servlet/SimpleCookie.html)。

根据servlet规范,Cookie的默认名称是JSESSIONID。 此外,Shiro的cookie支持HttpOnly标志。 sessionIdCookie默认设置HttpOnly为true,以获得额外的安全性。

注意:即使在Servlet 2.4和2.5环境中,Shiro的Cookie概念也支持HttpOnly标志(而Servlet API中仅在2.6或更高版本才本地化支持该标志属性)。

5)禁用会话cookie

这个很简单,如下代码所示:

5. “记住我”服务

如果AuthenticationToken实现org.apache.shiro.authc.RememberMeAuthenticationToken接口,Shiro将执行'rememberMe'服务。 该接口指定一个方法:

booleanisRememberMe();

如果此方法返回true,则Shiro将记住跨会话的终端用户身份。

注意:UsernamePasswordToken和RememberMe

常用的UsernamePasswordToken已经实现了RememberMeAuthenticationToken接口并支持rememberMe登录。

5.1编程式支持

要以编程方式使用rememberMe,可以在支持此配置的类上将该值设置为true。 例如,使用标准的UsernamePasswordToken:

5.2 基于表单登录

对于Web应用程序,authc过滤器默认为FormAuthenticationFilter。 这支持读取'rememberMe'布尔值作为表单/请求参数。 默认情况下,它期望请求参数被命名为rememberMe。 下面是一个支持这个的shiro.ini配置示例:

然后,在你的网页中,有一个名为'rememberMe'的复选框

默认情况下,FormAuthenticationFilter将查找名为username,password和rememberMe的请求参数。 如果这些与您在表单中使用的表单字段名称不同,则需要在FormAuthenticationFilter上配置名称。 例如,在shiro.ini中:

5.3 Cookie配置

您可以通过设置默认的RememberMeManager的各种cookie属性来配置rememberMe cookie的功能。 例如,在shiro.ini中:

可以从JavaDOC上查看CookieRememberMeManager和支持SimpleCookie的配置属性。

5.4 定制RememberMeManager

应该注意的是,如果默认的基于Cookie的RememberMeManager实现不能满足您的需求,您可以插入任何您喜欢的安全管理器,如您配置任何其他对象引用一样:

6. Shiro标签库(JSP/GSP标签库)

Apache Shiro提供了一个主体感知JSP/GSP标签库,允许您根据当前Subject的状态来控制您的JSP、JSTL或GSP页面输出。 这对于基于身份标识和授权的当前用户状态的个性化页面查询非常有用。

6.1 标签库配置

标记库描述符(TLD)文件捆绑在META-INF/shiro.tld文件的shiro-web.jar中。 要使用任何标签,请将以下行添加到JSP页面的顶部(或者您定义页面指令的任何位置):

%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

我们使用shiro前缀来表示shiro标签库名称空间,但您可以指定您喜欢的任何名称。

现在我们将覆盖来讨论每个标签并展示它如何用于渲染页面。

1- guest标签

只有当前主体被视为"访客"时,guest标记才会显示其包裹的内容。 访客是任何没有身份的主体。 也就是说,我们不知道用户是谁,因为他们没有登录,并且他们没有从以前的网站访问中被记住(从“记住我服务”中)。例如:

guest标签与user标签的逻辑是相反的。

2- user标签

只有当前主体被认为是'用户'时,用户标签才会显示其包装内容。 在这种情况下,'用户'被定义为具有已知身份的主体,无论是来自成功认证还是来自'RememberMe'服务。 请注意,这个标签在语义上与authenticated标签不同,它比user标签更具限制性。例如:

User标签与guest标签的逻辑是相反的。

3- Authenticated标签

仅在当前用户在当前会话期间成功通过身份验证时才显示正文内容。 它比'user'标签更具限制性。 它在逻辑上与'notAuthenticated'标签相反。

仅当当前主体已在当前会话期间成功通过身份验证时,authenticated标记才会显示其包装内容。 这是比用户更严格的标签,用于保证敏感工作流中的身份。例如:

Authenticated标签与notauthenticated标签逻辑相反。

4- notAuthenticated标签

如果当前主体尚未在当前会话中成功通过身份验证,则notAuthenticated标记将显示其包装的内容。例如:

5- principal标签

principal标签将输出主体的主题(识别属性)或该主体的属性。

没有任何标签属性,标签将渲染主题的toString()值。 例如(假设主题是一个字符串username),例如:

Hello, shiro:principal/>, how are you today?

几乎相等的表达如下:

Hello, %=SecurityUtils.getSubject().getPrincipal().toString() %>, how are you today?

1)典型主题

principal标签默认假设要打印的主题是subject.getPrincipal()值。 但是,如果您想打印一个不是主要主题的值,而是打印主体的主要集合中的另一个值,则可以按类型获取该主体并打印该值。

例如,打印主体的用户标识(而不是用户名),假定标识在主体集合中:

User ID: principaltype="java.lang.Integer"/>

几乎与下列语句的表达相等:

User ID: %=SecurityUtils.getSubject().getPrincipals().oneByType(Integer.class).toString() %>

2)主题属性

但是如果主题(上面的默认主要主题或"类型"主题)是复杂的对象而不是简单的字符串,您想如何引用该主题的某个属性呢? 您可以使用property属性来指示要读取的属性的名称(必须可通过JavaBeans兼容的getter方法访问)。 例如(假设主要的主体是一个用户对象)

Hello, shiro:principalproperty="firstName"/>, how are you today?

上面的代码,几乎等同于下列的表达:

Hello, %=SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>, how are you today?

或者,结合type属性的表达如下:

Hello, shiro:principaltype="com.foo.User" property="firstName"/>, how are you today?

这大致相当于以下内容:

Hello, %=SecurityUtils.getSubject().getPrincipals().oneByType(com.foo.User.class).getFirstName().toString() %>, how are you today?

6- hasRole标签

只有当当前主体被分配了指定角色时,hasRole标签才会显示其包装内容。例如:

hasRole标签与lacksRole逻辑相反。

7- lacksRole标签

仅当当前主体未被分配指定角色时,lacksRole标签才会显示其包装内容。例如:

8- hasAnyRole标签

如果当前主体被由逗号分隔的角色名称列表中的任意指定角色分配,则hasAnyRole标签将显示其包装内容。例如:

hasAnyRole标签当前不具有逻辑上相反的标签。

9- hasPermission标签

hasPermission标签只有在当前主体'具有'(暗含)指定权限时才会显示其包装内容。 也就是说,用户具有指定的能力。例如:

hasPermission标签与lacksPermission标签逻辑相反。

Ok,本篇关于Apache Shiro安全框架的web应用支持相关技术,就介绍到这里。

最后,就请您关注一下本号,点个赞、收藏和分享一下吧。^_*

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180430A0K88M00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券