9.5.1 使用 Spring Security 的 JSP 标签库

Spring Security 的 JSP 标签库很小,只包含三个标签,如表 9.6 所示。

JSP 标签

作用

<security:accesscontrollist>

如果用户通过访问控制列表授予了指定的权限,那么渲染该标签体中的内容

<security:authentication>

渲染当前用户认证对象的详细信息

<security:authorize>

如果用户被授予了特定的权限或者 SpEL 表达式的计算结果为 true,那么渲染该标签体中的内容

为了使用 JSP 标签库,我们需要在对应的 JSP 中声明它:

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

只要标签库在 JSP 文件中进行了声明,我们就可以使用它了。让我们看看 Spring Security 提供的这三个标签是如何工作的。

访问认证信息的细节

借助 Spring Security JSP 标签库,所能做到的最简单的一件事情就是便利地访问用户的认证信息。例如,对于 Web 站点来讲,在页面顶部以用户名标示显示 “欢迎” 或 “您好” 信息是很常见的。这恰恰是 <security:authentication> 能为我们所做的事情。例如:

Hello <security:authentication property="pricipal.username" />!

其中,property 用来标示用户认证对象的一个属性。可用的属性取决于用户认证的方式。但是,我们可以依赖几个通用的属性,在不同的认证方式下,它们都是可用的,如表 9.7 所示。

认证属性

描述

authorities

一组用于表示用户所授予权限的GrantedAuthority对象

Credentials

用于核实用户的凭证(通常,这会是用户的密码)

details

认证的附加信息(IP地址、证件序列号、会话ID等)

principal

用户的基本信息对象

在我们的示例中,实际上渲染的是 principal 属性中嵌套的 username 属性。

当像前面示例那样使用时,<security:authentication> 将在视图中渲染属性的值。但是如果你愿意将其赋值给一个变量,那只需要在 var 属性中指明变量的名字即可。例如,如下展现了如何将其设置给名为 loginId 的属性:

<security:authentication property="principal.username" var="loginId" />

这个变量默认是定义在页面作用域内的。但是如果你愿意在其他作用域内创建它,例如请求或会话作用域(或者是能够在 javax.servlet.jsp.PageContext 中获取的其他作用域),那么可以通过 scope 属性来声明。例如,要在请求作用域内创建这个 变量,那可以使用 <security:authentication> 按照如下的方式来设置:

<security:authentication property="principal.username" var="loginId" scope="request" />

<security:authentication> 标签非常有用,但这只是 Spring Security JSP 标签库功能的基础功能。让我们来看一下如何根据用户的权限来渲染内容。

条件性的渲染内容

有时候视图上的一部分内容需要根据用户被授予了什么权限来确定是否渲染。对于已经登录的用户显示登录表单,或者对还未登录的用户显示个性化的问候信息都是毫无意义的。

Spring Security <security:authorize>JSP 标签能够根据用户被授予的权限有条件地渲染页面的部分内容。例如,在 Spittr 应用中, 对于没有 ROLE_SPITTER 角色的用户,我们不会为其显示添加新 Spitter 记录的表单。程序清单 9.9 展现了如何使用标签来为具有 ROLE_SPITTER 角色的用户显示 Spitter 表单。

程序清单 9.9 使用标签基于 SpEL 进行有条件地渲染
<sec:authorize access="hasRole('ROLE_SPITTER')">
  <s:url value="/spittles" var="spittle_url" />
  <sf:form modelAttribute="spittle" action="${spittle_url}">
    <sf:label path="text">
      <s:message code="label.spittle" text="Enter spittle:" />
    </sf:label>
    <sf:textarea path="text" row="2" cols="40" />
    <sf:errors path="text" />
    <br/>
    <div class="spitItSubmit">
      <input type="submit" value="Spit it !" class="status-btn round-btn disabled" />
    </div>
  </sf:form>
</sec:authorize>

access 属性被赋值为一个 SpEL 表达式,这个表达式的值将确定 <security:authorize> 标签主体内的内容是否渲染。这里我们使用了 hasRole('ROLE_SPITTER') 表达式来确保用户具有 ROLE_SPITTER 角色。但是,当你设置 access 属性时,可以任意发挥 SpEL 的强大威力,包括表 9.5 所示的 Spring Security 所提供的表达式。

借助于这些可用的表达式,可以构造出非常有意思的安全性约束。例如,假设应用中有一些管理功能只能对用户名为 habuma 的用户可用。也许你会像这样使用 isAuthenticated() 和 principal 表达式:

<security:authorize access="isAuthenticated and principal.username=='habuma'">
  <a href="/admin">Administration</a>
</security:authorize>

我相信你能设计出比这个更有意思的表达式,可以尽情发挥你的想象力来构造更多的安全性约束。借助于 SpEL,选择其实是无限的。

但是我构造的这个示例还有一件事让人很困惑。尽管我想限制管理功能只能给 habuma 用户,但使用 JSP 标签表达式并不见得理想。确实,它能在视图上阻止链接的渲染。但是没有什么可以阻止别人在浏览器的地址栏手动输入 “/admin” 这个 URL。

根据我们在本章前面所学,这是一个很容易解决的问题。在安全配置中,添加一个对 antMatchers() 方法的调用将会严格限制对 “/admin” 这个 URL 的访问。

.antMatchers("/admin")
.access("isAuthenticated and principal.username='habuma'");

现在,管理功能已经被锁定了。URL 地址得到了保护,并且到这个 URL 的链接在用户没有授权使用的情况下不会显示。但是为了做到这一点,我们需要在两个地方声明 SpEL 表达式 —— 在安全配置中以及在 <security:authorize> 标签的 access 属性中。有没有办法消除这种重复性,并且还要确保只有规则条件满足的情况下才渲染管理功能的链接呢?

这是 <security:authorize> 的 url 属性所要做的事情。它不像 access 属性那样明确声明安全性限制,url 属性对一个给定的 URL 模式会间接引用其安全性约束。鉴于我们已经在 Spring Security 配置中为 “/admin” 声明了安全性约束,所以我们可以这样使用 url 属性:

<security:authorize url="/admin">
  <spring:url value="/admin" var="admin_url" />
  <br/>
  <a href="${admin_url}">Admin</a>
</security:authorize>

因为只有基本信息中用户名为 “habuma” 的已认证用户才能访问 “/admin” URL,所以只有满足以上条件,<security:authorize> 标签主体中的内容才会被渲染。我们只在一个地方配置了表达式(安全配置中),但是在两个地方进行了应用。

Spring Security 的 JSP 标签库非常便利,尤其是只给满足条件的用户渲染特定的视图元素时更是如此。如果我们选择 Thymeleaf 而不是 JSP 作为视图方案的话,我们其实还能延续这种好运气。我们已经看到 Thymeleaf 的 Spring 方言能够自动为表单添加隐藏的 CSRF token,现在我们看一下 Thymeleaf 如何支持Spring Security。

Last updated