Spring Security Config : HttpSecurity安全配置器 ExpressionUrlAuthorizationConfigurer

概述

介绍

作为一个配置HttpSecuritySecurityConfigurer,ExpressionUrlAuthorizationConfigurer的配置任务如下 :

  • 配置如下安全过滤器Filter
    • FilterSecurityInterceptor

ExpressionUrlAuthorizationConfigurer可以为多组 RequestMatcher 分别配置不同的权限属性。这里每组RequestMatcher表示一组调用者想设定成相同权限控制的Http method/URL pattern,这里所设置的权限属性其实是基于SpEL的权限表达式。ExpressionUrlAuthorizationConfigurer可以接收一个调用者指定的安全表达式处理器来理解这些权限表达式。如果调用者不指定,则使用缺省的安全表达式处理器DefaultWebSecurityExpressionHandler来理解这些权限表达式。

ExpressionUrlAuthorizationConfigurer继承自AbstractInterceptUrlConfigurer。作为一个安全配置器,它们对目标安全构建器HttpSecurity的主要配置逻辑实现在AbstractInterceptUrlConfigurer#configureExpressionUrlAuthorizationConfigurer主要是根据基类AbstractInterceptUrlConfigurer的定义,提供相应的抽象方法的实现。

继承关系

AbstractInterceptUrlConfigurer

使用

例子 1

    // HttpSecurity 代码片段
	public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
			throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
				.getRegistry();
	}

例子 2 : 请求任意URL的访问者必须拥有指定权限

 // 该例子使用 ExpressionUrlAuthorizationConfigurer 配置请求任何一个URL的访问者必须拥有 USER 权限
 @Configuration
 @EnableWebSecurity
 public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
                                .and().withUser("admin").password("password").roles("ADMIN", "USER");
        }
 }

例子 3 : 请求不同URL的访问者必须拥有相应权限

// 该例子使用 ExpressionUrlAuthorizationConfigurer 配置 : 
// 1. 请求 /admin/** 这种URL的访问者必须拥有 ADMIN 权限,
// 2. 请求 /admin/** 这种URL之外的其他任意URL 的访问者必须拥有 USER 权限,
 @Configuration
 @EnableWebSecurity
 public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
                                .antMatchers("/**").hasRole("USER").and().formLogin();
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
                                .and().withUser("admin").password("password").roles("ADMIN", "USER");
        }
 }

源代码

源代码版本 Spring Security Config 5.1.4.RELEASE

package org.springframework.security.config.annotation.web.configurers;

// 省略 imports


public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
		extends
		AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {
	static final String permitAll = "permitAll";
	private static final String denyAll = "denyAll";
	private static final String anonymous = "anonymous";
	private static final String authenticated = "authenticated";
	private static final String fullyAuthenticated = "fullyAuthenticated";
	private static final String rememberMe = "rememberMe";

   // 本安全配置器所使用的 URL pattern 和 所需权限 的注册表,也是映射表,
   // 使用实现类为 ExpressionInterceptUrlRegistry, 一个本类的内部实现类,
   // 继承自基类的 AbstractInterceptUrlRegistry ,通过该注册表类可以为当前
   // 安全配置器指定一个安全表达式处理器 SecurityExpressionHandler<FilterInvocation>,
   // 也就是指定下面的属性变量 expressionHandler
	private final ExpressionInterceptUrlRegistry REGISTRY;

	private SecurityExpressionHandler<FilterInvocation> expressionHandler;

	/**
	 * Creates a new instance
	 * @see HttpSecurity#authorizeRequests()
	 */
	public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) {
       //  在构造函数中创建 REGISTRY 为一个 ExpressionInterceptUrlRegistry 实例
		this.REGISTRY = new ExpressionInterceptUrlRegistry(context);
	}

    
	public ExpressionInterceptUrlRegistry getRegistry() {
		return REGISTRY;
	}

    // 内部嵌套类,非静态类,本安全配置器所使用的 URL pattern 和 所需权限 的注册表,也是映射表,
    // 的实现类,继承自基类的 AbstractInterceptUrlRegistry ,通过该注册表类可以为当前
   // 安全配置器指定一个安全表达式处理器 SecurityExpressionHandler<FilterInvocation>
	public class ExpressionInterceptUrlRegistry
			extends ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry
			<ExpressionInterceptUrlRegistry, AuthorizedUrl> {

		/**
		 * @param context
		 */
		private ExpressionInterceptUrlRegistry(ApplicationContext context) {
			setApplicationContext(context);
		}

       // 针对指定的 HTTP method, 和 mvcPatterns 构造一组 RequestMatcher, 包装在一个 
       // MvcMatchersAuthorizedUrl 对象中,最终这组 RequestMatcher 上会应用相同的
       // 权限设置
		@Override
		public MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method, String... mvcPatterns) {
			return new MvcMatchersAuthorizedUrl(createMvcMatchers(method, mvcPatterns));
		}

       // 针对指定的 patterns 构造一组 RequestMatcher, 包装在一个 
       // MvcMatchersAuthorizedUrl 对象中,最终这组 RequestMatcher 上会应用相同的
       // 权限设置
       // 本方法其实是使用了上面的方法
       // MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method, String... mvcPatterns)
		@Override
		public MvcMatchersAuthorizedUrl mvcMatchers(String... patterns) {
			return mvcMatchers(null, patterns);
		}

		@Override
		protected final AuthorizedUrl chainRequestMatchersInternal(
				List<RequestMatcher> requestMatchers) {
			return new AuthorizedUrl(requestMatchers);
		}

		/**
		 * Allows customization of the SecurityExpressionHandler to be used. The
		 * default is DefaultWebSecurityExpressionHandler
		 * 为当前 ExpressionInterceptUrlRegistry 设置安全表达式处理器,如果不设置
         * 缺省值为一个 DefaultWebSecurityExpressionHandler 
		 * @param expressionHandler the SecurityExpressionHandler to be used
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization.
		 */
		public ExpressionInterceptUrlRegistry expressionHandler(
				SecurityExpressionHandler<FilterInvocation> expressionHandler) {
			ExpressionUrlAuthorizationConfigurer.this.expressionHandler = expressionHandler;
			return this;
		}

		/**
		 * Adds an ObjectPostProcessor for this class.
		 *
		 * @param objectPostProcessor
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customizations
		 */
		public ExpressionInterceptUrlRegistry withObjectPostProcessor(
				ObjectPostProcessor<?> objectPostProcessor) {
			addObjectPostProcessor(objectPostProcessor);
			return this;
		}

		public H and() {
			return ExpressionUrlAuthorizationConfigurer.this.and();
		}

	}

	/**
	 * Allows registering multiple RequestMatcher instances to a collection of
	 * ConfigAttribute instances
	 *
	 * 注册一组 RequestMatcher requestMatchers,这组 requestMatchers 中每一个
	 * RequestMatcher 都会映射到所需权限 configAttributes
	 * @param requestMatchers the RequestMatcher instances to register to the
	 * ConfigAttribute instances
	 * @param configAttributes the ConfigAttribute to be mapped by the
	 * RequestMatcher instances
	 */
	private void interceptUrl(Iterable<? extends RequestMatcher> requestMatchers,
			Collection<ConfigAttribute> configAttributes) {
		for (RequestMatcher requestMatcher : requestMatchers) {
			// 将 requestMatchers 中每一个RequestMatcher 和 configAttributes 构造一个
			// UrlMapping 对象,也就是 <URL pattern,所需权限>对儿,添加到注册表 REGISTRY
			REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(
					requestMatcher, configAttributes));
		}
	}

    // 基类定义的抽象方法,要求实现类必须提供实现。
    // 这里提供实现,创建缺省 AccessDecisionManager 时要用。
    // 这里的实现其实提供了一个 WebExpressionVoter, 使用设置给本类的安全表达式处理器
    // 或者缺省的安全表达式处理器,具体由方法 getExpressionHandler 决定
	@Override
	@SuppressWarnings("rawtypes")
	final List<AccessDecisionVoter<? extends Object>> getDecisionVoters(H http) {
		List<AccessDecisionVoter<? extends Object>> decisionVoters = 
            new ArrayList<AccessDecisionVoter<? extends Object>>();
		WebExpressionVoter expressionVoter = new WebExpressionVoter();
		expressionVoter.setExpressionHandler(getExpressionHandler(http));
		decisionVoters.add(expressionVoter);
		return decisionVoters;
	}
    // 基类定义的抽象方法,要求实现类必须提供实现。
    // 这里提供实现,创建目标安全拦截过滤器 FilterSecurityInterceptor 时要用。
    // 这里的实现基于调用者提供的配置所形成的 <URL pattern,所需权限>映射信息,也就是 
    // REGISTRY 生成一个 ExpressionBasedFilterInvocationSecurityMetadataSource 对象,
    // 该对象会最终被设置到目标安全拦截过滤器 FilterSecurityInterceptor 上。
    // FilterSecurityInterceptor 会在处理一个请求时,通过该 
    // ExpressionBasedFilterInvocationSecurityMetadataSource 对象获取被请求URL所需的权限,
    // 另外获取请求者所拥有的的权限,从而判断请求这是否可以访问相应资源。
	@Override
	final ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource(
			H http) {
		LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = REGISTRY
				.createRequestMap();
		if (requestMap.isEmpty()) {
			throw new IllegalStateException(
	"At least one mapping is required (i.e. authorizeRequests().anyRequest().authenticated())");
		}
		return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap,
				getExpressionHandler(http));
	}

	private SecurityExpressionHandler<FilterInvocation> getExpressionHandler(H http) {
       // 如果调用者设置了属性  expressionHandler 使用之,
       // 否则使用缺省值 DefaultWebSecurityExpressionHandler
		if (expressionHandler == null) {
          // 创建缺省 DefaultWebSecurityExpressionHandler 对象,设置到 expressionHandler 属性
			DefaultWebSecurityExpressionHandler defaultHandler = 
				new DefaultWebSecurityExpressionHandler();
			AuthenticationTrustResolver trustResolver = http
					.getSharedObject(AuthenticationTrustResolver.class);
			if (trustResolver != null) {
				defaultHandler.setTrustResolver(trustResolver);
			}
			ApplicationContext context = http.getSharedObject(ApplicationContext.class);
			if (context != null) {
              // 检查是否有 RoleHierarchy bean 可以应用,若检测到则应用
				String[] roleHiearchyBeanNames = context.getBeanNamesForType(RoleHierarchy.class);
				if (roleHiearchyBeanNames.length == 1) {
					defaultHandler.setRoleHierarchy(context.getBean(roleHiearchyBeanNames[0], 
						RoleHierarchy.class));
				}
              
              // 检测是否有 GrantedAuthorityDefaults bean 可以应用,若检测到则应用
				String[] grantedAuthorityDefaultsBeanNames = 
					context.getBeanNamesForType(GrantedAuthorityDefaults.class);
				if (grantedAuthorityDefaultsBeanNames.length == 1) {
					GrantedAuthorityDefaults grantedAuthorityDefaults = 
                        context.getBean(grantedAuthorityDefaultsBeanNames[0], 
                        	GrantedAuthorityDefaults.class);
					defaultHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
				}
              
              // 检测是否有 PermissionEvaluator bean 可以应用,若检测到则应用
				String[] permissionEvaluatorBeanNames = 
					context.getBeanNamesForType(PermissionEvaluator.class);
				if (permissionEvaluatorBeanNames.length == 1) {
					PermissionEvaluator permissionEvaluator = 
						context.getBean(permissionEvaluatorBeanNames[0], 
                        PermissionEvaluator.class);
					defaultHandler.setPermissionEvaluator(permissionEvaluator);
				}
			}
          
          // 新建对象的后置处理
			expressionHandler = postProcess(defaultHandler);
		}

		return expressionHandler;
	}

    // 表达式字符串构建工厂方法
	private static String hasAnyRole(String... authorities) {
		String anyAuthorities = StringUtils.arrayToDelimitedString(authorities,
				"','ROLE_");
		return "hasAnyRole('ROLE_" + anyAuthorities + "')";
	}

   // 表达式字符串构建工厂方法
	private static String hasRole(String role) {
		Assert.notNull(role, "role cannot be null");
		if (role.startsWith("ROLE_")) {
			throw new IllegalArgumentException(
					"role should not start with 'ROLE_' since it is automatically inserted. Got '"
							+ role + "'");
		}
		return "hasRole('ROLE_" + role + "')";
	}

    // 表达式字符串构建工厂方法
	private static String hasAuthority(String authority) {
		return "hasAuthority('" + authority + "')";
	}

    // 表达式字符串构建工厂方法
	private static String hasAnyAuthority(String... authorities) {
		String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
		return "hasAnyAuthority('" + anyAuthorities + "')";
	}

    // 表达式字符串构建工厂方法
	private static String hasIpAddress(String ipAddressExpression) {
		return "hasIpAddress('" + ipAddressExpression + "')";
	}

	/**
	 * An AuthorizedUrl that allows optionally configuring the
	 * MvcRequestMatcher#setMethod(HttpMethod)
	 *
	 * @author Rob Winch
	 */
	public class MvcMatchersAuthorizedUrl extends AuthorizedUrl {
		/**
		 * Creates a new instance
		 *
		 * @param requestMatchers the RequestMatcher instances to map
		 */
		private MvcMatchersAuthorizedUrl(List<MvcRequestMatcher> requestMatchers) {
			super(requestMatchers);
		}

		public AuthorizedUrl servletPath(String servletPath) {
			for (MvcRequestMatcher matcher : (List<MvcRequestMatcher>) getMatchers()) {
				matcher.setServletPath(servletPath);
			}
			return this;
		}
	}

    // 非静态内部类,主要是提供一组方法,便于往当前 ExpressionUrlAuthorizationConfigurer 中
    // 添加一组需要共同权限设置的 RequestMatcher,并对这组 RequestMatcher 设置相同的权限控制。
	public class AuthorizedUrl {
		private List<? extends RequestMatcher> requestMatchers;
		private boolean not;

		/**
		 * Creates a new instance
		 *
		 * @param requestMatchers the RequestMatcher instances to map
		 */
		private AuthorizedUrl(List<? extends RequestMatcher> requestMatchers) {
			this.requestMatchers = requestMatchers;
		}

		protected List<? extends RequestMatcher> getMatchers() {
			return this.requestMatchers;
		}

		/**
		 * Negates the following expression.
		 *
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public AuthorizedUrl not() {
			this.not = true;
			return this;
		}

		/**
		 * Shortcut for specifying URLs require a particular role. If you do not want to
		 * have "ROLE_" automatically inserted see #hasAuthority(String).
		 *
		 * @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not
		 * start with "ROLE_" as this is automatically inserted.
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry hasRole(String role) {
			return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
		}

		/**
		 * Shortcut for specifying URLs require any of a number of roles. If you do not
		 * want to have "ROLE_" automatically inserted see
		 * #hasAnyAuthority(String...)
		 *
		 * @param roles the roles to require (i.e. USER, ADMIN, etc). Note, it should not
		 * start with "ROLE_" as this is automatically inserted.
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry hasAnyRole(String... roles) {
			return access(ExpressionUrlAuthorizationConfigurer.hasAnyRole(roles));
		}

		/**
		 * Specify that URLs require a particular authority.
		 *
		 * @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry hasAuthority(String authority) {
			return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
		}

		/**
		 * Specify that URLs requires any of a number authorities.
		 *
		 * @param authorities the requests require at least one of the authorities (i.e.
		 * "ROLE_USER","ROLE_ADMIN" would mean either "ROLE_USER" or "ROLE_ADMIN" is
		 * required).
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry hasAnyAuthority(String... authorities) {
			return access(ExpressionUrlAuthorizationConfigurer
					.hasAnyAuthority(authorities));
		}

		/**
		 * Specify that URLs requires a specific IP Address or subnet.
		 *
		 * @param ipaddressExpression the ipaddress (i.e. 192.168.1.79) or local subnet
		 * (i.e. 192.168.0/24)
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry hasIpAddress(String ipaddressExpression) {
			return access(ExpressionUrlAuthorizationConfigurer
					.hasIpAddress(ipaddressExpression));
		}

		/**
		 * Specify that URLs are allowed by anyone.
		 *
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry permitAll() {
			return access(permitAll);
		}

		/**
		 * Specify that URLs are allowed by anonymous users.
		 *
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry anonymous() {
			return access(anonymous);
		}

		/**
		 * Specify that URLs are allowed by users that have been remembered.
		 *
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 * @see RememberMeConfigurer
		 */
		public ExpressionInterceptUrlRegistry rememberMe() {
			return access(rememberMe);
		}

		/**
		 * Specify that URLs are not allowed by anyone.
		 *
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry denyAll() {
			return access(denyAll);
		}

		/**
		 * Specify that URLs are allowed by any authenticated user.
		 *
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry authenticated() {
			return access(authenticated);
		}

		/**
		 * Specify that URLs are allowed by users who have authenticated and were not
		 * "remembered".
		 *
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 * @see RememberMeConfigurer
		 */
		public ExpressionInterceptUrlRegistry fullyAuthenticated() {
			return access(fullyAuthenticated);
		}

		/**
		 * Allows specifying that URLs are secured by an arbitrary expression
		 *
		 * @param attribute the expression to secure the URLs (i.e.
		 * "hasRole('ROLE_USER') and hasRole('ROLE_SUPER')")
		 * @return the ExpressionUrlAuthorizationConfigurer for further
		 * customization
		 */
		public ExpressionInterceptUrlRegistry access(String attribute) {
			if (not) {
				attribute = "!" + attribute;
			}
			interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
			return ExpressionUrlAuthorizationConfigurer.this.REGISTRY;
		}
	}
}

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值