32. JSP 标签库

Spring Security 有自己的标签库,它为访问安全信息和在 JSP 中应用安全约束提供基本支持。

32.1 声明标签库

要使用任何标记,必须在 JSP 中声明安全标签库:

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

32.2 授权标签

这个标签被用来确定它的内容是否应该被评估。在 Spring Security 3.0 中,它可以以两种方式使用 [21]。第一种方法使用在标签的 access 属性中指定的 web 安全表达式。表达式计算将被委托给在应用程序上下文中定义的 SecurityExpressionHandler<FilterInvocation>(你应该在 <http> 命名空间配置中启用 web 表达式以确保此服务可用)。例如,你可能有

<sec:authorize access="hasRole('supervisor')">

This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s.

</sec:authorize>

当用于与 Spring Security 的 PermissionEvaluator 进行组合时,该标记还可以用于检查权限。例如:

<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')">

This content will only be visible to users who have read or write permission to the Object found as a request attribute named "domain".

</sec:authorize>

一个常见的要求是只显示一个特定的链接,如果用户被允许点击它。我们怎样才能事先确定某事是否被允许?这个标签也可以在另一种模式下操作,它允许你将一个特定的 URL 定义为属性。如果允许用户调用该 URL,则将评估标记体,否则将跳过该标记体。所以你可能会有点像

<sec:authorize url="/admin">

This content will only be visible to users who are authorized to send requests to the "/admin" URL.

</sec:authorize>

要使用这个标签,在应用程序上下文中也必须有 WebInvocationPrivilegeEvaluator 的实例。如果你正在使用命名空间,则将自动注册。这是 DefaultWebInvocationPrivilegeEvaluator 的实例,它为提供的 URL 创建虚拟 web 请求,并调用安全拦截器以查看请求是成功还是失败。这允许你委托给在 <http> 命名空间配置中使用 intercept-url 声明定义的访问控制设置,并节省了在 JSP 中复制信息(例如所需的角色)的时间。此方法还可以与提供 HTTP 方法的 method 属性相结合,用于更具体的匹配。

通过将 var 属性设置为变量名,可以把评估标记的 Boolean 结果(无论是否授予访问权限)存储在页面上下文范围变量中,从而避免在页面中的其他点重复和重新评估条件。

32.2.1 测试中禁用标签授权

在页面中隐藏未授权用户的链接并不能阻止他们访问 URL。例如,他们可以直接键入浏览器。作为测试过程的一部分,你可能希望揭示隐藏区域,以便检查链接在后端是否真正安全。如果将系统属性 spring.security.disableUISecurity 设置为 true,则 authorize 标签仍将运行,但不会隐藏其内容。默认情况下,它还将围绕 <span class="securityHiddenUI">…​</span> 标签包围该内容。这允许你用特定的CSS样式显示隐藏的内容,例如不同的背景颜色。例如,尝试运行具有该属性的教程 "tutorial" 应用程序。

如果希望更改默认 span 标签中的围绕文本(或者使用空字符串完全删除它),还可以设置属性 spring.security.securedUIPrefix 和 spring.security.securedUISuffix。

32.3 authentication 标签

此标签允许访问存储在安全上下文中的当前 Authentication 对象。它直接在 JSP 中呈现对象的属性。因此,例如,如果 Authentication 的 principal 属性是 Spring Security 的 UserDetails 对象的实例,那么使用 <sec:authentication property="principal.username" /> 将呈现当前用户的名称。

当然,对于这类事情,没有必要使用 JSP 标签,有些人喜欢在视图中保持尽可能少的逻辑。你可以访问 MVC 控制器中的 Authentication 对象(通过调用 SecurityContextHolder.getContext().getAuthentication()),并将数据直接添加到模型中以便由视图呈现。

32.4 访问控制列表标签

此标签仅在与 Spring Security 的 ACL 模块一起使用时才有效。它检查指定域对象所需权限的逗号分隔列表。如果当前用户拥有所有这些权限,则将对标签主体进行评估。如果他们没有,它将被跳过。一个例子可能是

[Caution] 警告

一般来说,这个标签应该被视为弃权。取而代之的是使用 小节 32.2, “authorize 标签”。

<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}">

This will be shown if the user has all of the permissions represented by the values "1" or "2" on the given object.

</sec:accesscontrollist>

这些权限被传递给在应用程序上下文中定义的 PermissionFactory,将它们转换为 ACL Permission 实例,因此它们可以是工厂支持的任何格式 - 它们不必是整数,可以是像 READ 或 WRITE 这样的字符串。如果找不到 PermissionFactory,将使用 DefaultPermissionFactory 的实例。应用程序上下文中的 AclService 将用于加载所提供的对象的 Acl 实例。Acl 将被调用所需的权限,以检查它们是否被授予。

此标记还支持 var 属性,与 authorize 标签相同。

32.5 csrfInput 标签

如果启用了 CSRF 保护,则此标签插入一个隐藏表单字段,该字段具有 CSRF 保护令牌的正确名称和值。如果没有启用 CSRF 保护,此标签将不输出任何内容。

通常,Spring Security 会自动为你使用的任何 <form:form> 标签插入 CSRF 表单字段,但是如果由于某种原因您不能使用 <form:form>,则 csrfInput 是一个方便的替换。

你应该把这个标签放在一个HTML <form></form> 块中,在那里你通常会放置其他的输入字段。不要将此标签放置在 Spring <form:form></form:form> 块中。Spring Security 自动处理 Spring 表单。

	<form method="post" action="/do/something">
		<sec:csrfInput />
		Name:<br />
		<input type="text" name="name" />
		...
	</form>

32.6 csrfMetaTags 标签

如果启用了 CSRF 保护,则此标签插入包含 CSRF 保护令牌表单字段和头名以及 CSRF 保护令牌值的元标签。这些元标签对于在应用程序中使用 JavaScript 中的 CSRF 保护非常有用。

你应该将 csrfMetaTags 放置在 HTML <head></head> 块中,在这里你通常会放置其他元标签。使用此标签后,你可以很容易地使用 JavaScript 访问表单字段名称、报头名称和令牌值。本例中使用 jQuery 来简化任务。

<!DOCTYPE html>
<html>
	<head>
		<title>CSRF Protected JavaScript Page</title>
		<meta name="description" content="This is the description for this page" />
		<sec:csrfMetaTags />
		<script type="text/javascript" language="javascript">

			var csrfParameter = $("meta[name='_csrf_parameter']").attr("content");
			var csrfHeader = $("meta[name='_csrf_header']").attr("content");
			var csrfToken = $("meta[name='_csrf']").attr("content");

			// using XMLHttpRequest directly to send an x-www-form-urlencoded request
			var ajax = new XMLHttpRequest();
			ajax.open("POST", "http://www.example.org/do/something", true);
			ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data");
			ajax.send(csrfParameter + "=" + csrfToken + "&name=John&...");

			// using XMLHttpRequest directly to send a non-x-www-form-urlencoded request
			var ajax = new XMLHttpRequest();
			ajax.open("POST", "http://www.example.org/do/something", true);
			ajax.setRequestHeader(csrfHeader, csrfToken);
			ajax.send("...");

			// using JQuery to send an x-www-form-urlencoded request
			var data = {};
			data[csrfParameter] = csrfToken;
			data["name"] = "John";
			...
			$.ajax({
				url: "http://www.example.org/do/something",
				type: "POST",
				data: data,
				...
			});

			// using JQuery to send a non-x-www-form-urlencoded request
			var headers = {};
			headers[csrfHeader] = csrfToken;
			$.ajax({
				url: "http://www.example.org/do/something",
				type: "POST",
				headers: headers,
				...
			});

		<script>
	</head>
	<body>
		...
	</body>
</html>

如果没有启用 CSRF 保护,csrfMetaTags 不输出任何内容。



[21] Spring Security 2.0 的遗留选项也受到支持,但不推荐。