Spring Security Web : DefaultWebSecurityExpressionHandler 缺省Web安全表达式处理器

探讨了Spring Security中DefaultWebSecurityExpressionHandler的作用与实现,它是处理Web安全表达式的处理器,基于缺省配置和环境对表达式求值,用于访问控制决策。

概述

DefaultWebSecurityExpressionHandlerSpring Security Web用于Web安全表达式处理器(handler)。它会基于一组缺省配置,和当时的环境,对指定的Web安全表达式求值。

DefaultWebSecurityExpressionHandler对给定的认证token和请求上下文FilterInvocation创建一个评估上下文EvaluationContext。然后供SPEL求值使用。比如在WebExpressionVoter中它被这么应用 :

public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
	// expressionHandler 缺省使用 DefaultWebSecurityExpressionHandler
	private SecurityExpressionHandler<FilterInvocation> expressionHandler = 
		new DefaultWebSecurityExpressionHandler();

	public int vote(Authentication authentication, FilterInvocation fi,
			Collection<ConfigAttribute> attributes) {
		assert authentication != null;
		assert fi != null;
		assert attributes != null;

		// 获取Web安全表达式配置属性
		WebExpressionConfigAttribute weca = findConfigAttribute(attributes);

		if (weca == null) {
			// 如果没有相应配置,返回 ACCESS_ABSTAIN:0 表示弃权
			return ACCESS_ABSTAIN;
		}
		// 缺省使用 DefaultWebSecurityExpressionHandler 创建 EvaluationContext
		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
				fi);
		ctx = weca.postProcess(ctx, fi);

		// 获取Web安全表达式配置属性weca中的表达式,和上面所创建的表达式求值上下文ctx,
		// 对其进行求值,结果按 boolean 类型处理
		// 如果求值结果为 true, 返回 ACCESS_GRANTED:1, 否则返回 ACCESS_DENIED:-1
		return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
				: ACCESS_DENIED;
	}

源代码解析

源代码版本 : 5.1.2.RELEASE

package org.springframework.security.web.access.expression;

import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionOperations;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.Assert;


public class DefaultWebSecurityExpressionHandler extends
		AbstractSecurityExpressionHandler<FilterInvocation> implements
		SecurityExpressionHandler<FilterInvocation> {

	// 用于识别一个Authentication对象是否 anonymous, rememberMe
	// 缺省使用AuthenticationTrustResolverImpl
	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

	// 缺省使用的角色前缀
	private String defaultRolePrefix = "ROLE_";

	// 对给定的认证token authentication 和给定的请求上下文 fi 构建 SecurityExpressionOperations,
	// 此 SecurityExpressionOperations 用于进一步构建 EvaluationContext 对象
	@Override
	protected SecurityExpressionOperations createSecurityExpressionRoot(
			Authentication authentication, FilterInvocation fi) {	
		WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
		root.setPermissionEvaluator(getPermissionEvaluator());
		root.setTrustResolver(trustResolver);
		root.setRoleHierarchy(getRoleHierarchy());
		root.setDefaultRolePrefix(this.defaultRolePrefix);
		return root;
	}

	/**
	 * Sets the  AuthenticationTrustResolver to be used. The default is
	 * AuthenticationTrustResolverImpl.
	 *
	 * @param trustResolver the AuthenticationTrustResolver to use. Cannot be
	 * null.
	 */
	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
		Assert.notNull(trustResolver, "trustResolver cannot be null");
		this.trustResolver = trustResolver;
	}

	/**
	 * 
	 * Sets the default prefix to be added to 
	 * org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole(String...) 
	 * or org.springframework.security.access.expression.SecurityExpressionRoot#hasRole(String). 
	 * For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN")
	 * is passed in, then the role ROLE_ADMIN will be used when the defaultRolePrefix is
	 * "ROLE_" (default).
	 * 
	 * 设置表达式hasAnyRole(String...)或者hasRole(String)使用的角色前缀。不调用该方法,则使用缺省值
	 * "ROLE_"。
	 * 
	 * If null or empty, then no default role prefix is used.
	 * 如果调用了该方法,设置参数为 null 或者 "", 表明不使用角色前缀。
	 *
	 * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
	 */
	public void setDefaultRolePrefix(String defaultRolePrefix) {
		this.defaultRolePrefix = defaultRolePrefix;
	}
}

DefaultWebSecurityExpressionHandler继承自AbstractSecurityExpressionHandler,真正创建EvaluationContext的方法也是现在该类中:

package org.springframework.security.access.expression;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;

/**
 * Base implementation of the facade which isolates Spring Security's requirements for
 * evaluating security expressions from the implementation of the underlying expression
 * objects.
 * Spring Security安全表达式求值实现的通用逻辑基类,同具体某种底层安全表达式实现,比如Web安全,隔离开来。
 * 
 * @author Luke Taylor
 * @since 3.1
 */
public abstract class AbstractSecurityExpressionHandler<T> implements
		SecurityExpressionHandler<T>, ApplicationContextAware {
	// 缺省使用 spel parser	
	private ExpressionParser expressionParser = new SpelExpressionParser();
	private BeanResolver br;
	private RoleHierarchy roleHierarchy;
	// 缺省使用 denyAll 评估器
	private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();

	public final ExpressionParser getExpressionParser() {
		return expressionParser;
	}

	public final void setExpressionParser(ExpressionParser expressionParser) {
		Assert.notNull(expressionParser, "expressionParser cannot be null");
		this.expressionParser = expressionParser;
	}

	/**
	 * Invokes the internal template methods to create  StandardEvaluationContext
	 * and SecurityExpressionRoot objects.
	 *
	 * @param authentication the current authentication object
	 * @param invocation the invocation (filter, method, channel)
	 * @return the context object for use in evaluating the expression, populated with a
	 * suitable root object.
	 */
	public final EvaluationContext createEvaluationContext(Authentication authentication,
			T invocation) {
		// createEvaluationContext 由子类提供具体实现,根据自己所服务的安全环境创建相应的
		// SecurityExpressionOperations 对象
		SecurityExpressionOperations root = createSecurityExpressionRoot(authentication,
				invocation);
		// 创建	EvaluationContext, 实现类使用标准实现 	StandardEvaluationContext
		StandardEvaluationContext ctx = createEvaluationContextInternal(authentication,
				invocation);
		// 表达式求值可能需要用到bean,这里指定bean解析器,通常指向整个Spring bean容器		
		ctx.setBeanResolver(br);
		// 设置 EvaluationContext 的根对象为上面创建的 SecurityExpressionOperations root
		ctx.setRootObject(root);

		return ctx;
	}

	/**
	 * Override to create a custom instance of StandardEvaluationContext.
	 * 一个StandardEvaluationContext或者其子类,
	 * 缺省是一个StandardEvaluationContext , 子类可以覆盖该方法提供一个自定义的
	 * StandardEvaluationContext子类实例
	 * 
	 * The returned object will have a SecurityExpressionRootPropertyAccessor
	 * added, allowing beans in the ApplicationContext to be accessed via
	 * expression properties.
	 *
	 * @param authentication the current authentication object
	 * @param invocation the invocation (filter, method, channel)
	 * @return A StandardEvaluationContext or potentially a custom subclass if
	 * overridden.
	 */
	protected StandardEvaluationContext createEvaluationContextInternal(
			Authentication authentication, T invocation) {
		return new StandardEvaluationContext();
	}

	/**
	 * Implement in order to create a root object of the correct type for the supported
	 * invocation type.
	 *
	 * @param authentication the current authentication object
	 * @param invocation the invocation (filter, method, channel)
	 * @return the object wh
	 */
	protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
			Authentication authentication, T invocation);

	protected RoleHierarchy getRoleHierarchy() {
		return roleHierarchy;
	}

	public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
		this.roleHierarchy = roleHierarchy;
	}

	protected PermissionEvaluator getPermissionEvaluator() {
		return permissionEvaluator;
	}

	public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
		this.permissionEvaluator = permissionEvaluator;
	}

	public void setApplicationContext(ApplicationContext applicationContext) {
		br = new BeanFactoryResolver(applicationContext);
	}
}

<think>嗯,用户遇到了JavaScript中的"Invalid left-hand side expression in prefix operation"错误,这属于左值引用问题。从引用内容看,用户可能尝试在控制台执行了类似++2或++[]这样的操作。 用户提供的四个引用都指向同一个核心问题:前置递增运算符(++)要求操作数必须是可修改的左值表达式。引用1和2展示了具体的错误案例,引用3详细解释了引用类型的要求,引用4则展示了另一个赋值场景下的类似错误。 这个错误通常发生在两种场景: 1. 尝试对原始值使用++:比如++2或++"hello",数字字面量和字符串原始值都不是可赋值的容器 2. 尝试对临时值使用++:比如++[]或++(a+b),这些表达式计算后不产生持久化的存储位置 解决方法很明确:确保++操作的是变量或对象属性。例如: - 错误:++2 → 正确:let x=2; x++; - 错误:++[] → 正确:let arr=[]; arr[0]++ 特别要注意引用3提到的关键点:++运算符要求操作数必须是引用(reference),而非值(value)。这解释了为什么[[]][0]++可以工作(它解析为数组元素的引用),而[]++会报错(整个数组字面量本身不是引用)。 用户可能是在调试时无意中写了这样的表达式,或者是误解了++运算符的运作机制。需要强调左值的概念——必须是内存中有明确存储位置的表达式。</think>## JavaScript 错误解析:`Invalid left-hand side expression in prefix operation` 这个错误表示你在使用**前置递增运算符 (`++`) 或前置递减运算符 (`--`)** 时,运算符右侧的表达式不能作为有效的“左值”。 ### 🧠 错误原因 1. **左值要求**:前置 `++`/`--` 运算符要求其操作数必须是一个**可修改的存储位置(左值)**。左值通常是: - 变量(如 `counter`) - 对象属性(如 `obj.value`) - 数组元素(如 `arr[0]`) 2. **无效操作数**:以下情况会触发此错误: - **原始值**:`++2`(数字字面量不可修改) - **临时值**:`++(a + b)`(表达式结果无存储位置) - **非引用字面量**:`++[]`(数组字面量本身不是可修改的引用) - **函数调用**:`++getValue()`(函数返回值不是存储位置) ### 🔧 解决方法 1. **确保操作数是变量或属性引用**: ```javascript // 错误 ❌ ++2; // 数字字面量 ++[]; // 数组字面量 ++(a + b); // 表达式结果 // 正确 ✅ let count = 0; ++count; // 操作变量 const obj = { value: 5 }; ++obj.value; // 操作对象属性 const arr = [10]; ++arr[0]; // 操作数组元素 ``` 2. **区分引用与值**: - 操作数必须是**引用**(指向内存位置),而非单纯的值。 - 示例:`[[]][0]` 是引用(指向内部数组),而 `[]` 是字面量值。 3. **特殊情况:数组元素引用**: ```javascript // 正确 ✅:`[[]][0]` 解析为内部数组的引用 ++[[]][0]; // 等效于:let inner = []; ++inner; // 错误 ❌:`[]` 是临时字面量 ++[]; ``` ### 💡 关键总结 | **场景** | **有效?** | **原因** | |-------------------|------------|------------------------------| | `++variable` | ✅ 有效 | 变量是明确的可修改存储位置 | | `++obj.property` | ✅ 有效 | 对象属性是可修改的引用 | | `++arr[index]` | ✅ 有效 | 数组元素是可修改的引用 | | `++123` | ❌ 无效 | 原始值字面量不可修改 | | `++[]` | ❌ 无效 | 数组字面量不是持久化引用 | | `++(x + y)` | ❌ 无效 | 表达式结果无内存存储位置 | > **核心原则**:前置 `++`/`--` 需要直接操作一个**可赋值的存储位置**(左值)。如果操作数不是变量、属性或数组元素等引用,引擎会抛出此错误。 ### 📚 相关问题 1. JavaScript 中左值(L-value)和右值(R-value)的区别是什么? 2. 如何避免 `Invalid left-hand side in assignment` 错误(赋值场景)? 3. JavaScript 中哪些运算符要求操作数必须是左值? 4. 为什么 `let x = 0; x++` 有效而 `0++` 无效? [^1]: 引用自浏览器控制台错误示例:`++2` 和 `++[]` 触发 `Invalid left-hand side expression in prefix operation`。 [^2]: 对比 `[[]][0]++`(有效)和 `[]++`(无效)的差异。 [^3]: 解释前置递增操作需要引用而非字面量,如 `++[]` 的无效性。 [^4]: 类似错误在赋值操作中的表现(`Invalid left-hand side in assignment`)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值