作为一个配置HttpSecurity
的SecurityConfigurer
,ExpressionUrlAuthorizationConfigurer
的配置任务如下 :
Filter
FilterSecurityInterceptor
ExpressionUrlAuthorizationConfigurer
可以为多组 RequestMatcher
分别配置不同的权限属性。这里每组RequestMatcher
表示一组调用者想设定成相同权限控制的Http method/URL pattern
,这里所设置的权限属性其实是基于SpEL
的权限表达式。ExpressionUrlAuthorizationConfigurer
可以接收一个调用者指定的安全表达式处理器来理解这些权限表达式。如果调用者不指定,则使用缺省的安全表达式处理器DefaultWebSecurityExpressionHandler
来理解这些权限表达式。
ExpressionUrlAuthorizationConfigurer
继承自AbstractInterceptUrlConfigurer
。作为一个安全配置器,它们对目标安全构建器HttpSecurity
的主要配置逻辑实现在AbstractInterceptUrlConfigurer#configure
。ExpressionUrlAuthorizationConfigurer
主要是根据基类AbstractInterceptUrlConfigurer
的定义,提供相应的抽象方法的实现。
// HttpSecurity 代码片段
public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
throws Exception {
ApplicationContext context = getContext();
return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
.getRegistry();
}
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");
}
}
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,
// 也就是指定下面的属性变量 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
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 对象,也就是 对儿,添加到注册表 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 时要用。
// 这里的实现基于调用者提供的配置所形成的 映射信息,也就是
// 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;
}
}
}