在使用Spring Security进行权限检查的时候会用到SecurityExpressionHandler,具体参考《【Spring-Security源码分析】Spring Security基于注解认证原理》。
SecurityExpressionHandler接口定义了两个方法。
public interface SecurityExpressionHandler extends AopInfrastructureBean {
ExpressionParser getExpressionParser();
EvaluationContext createEvaluationContext(Authentication authentication, T invocation);
}
ExpressionParser的parseExpression()方法接受一个表达式字符串然后返回一个Expression对象,通过这个对象的getValue()方法就可以返回这个表达式代表的结果值,这个方法有多个重载方法其中一个还可接收一个EvaluationContext对象,这个对象意味着在特定环境下解析表达式。具体使用请参考https://www.cnblogs.com/best/p/5748105.html。
Spring Security中AbstractSecurityExpressionHandler实现了此接口,提供了这两个方法的默认实现。
public abstract class AbstractSecurityExpressionHandler implements
SecurityExpressionHandler, ApplicationContextAware {
private ExpressionParser expressionParser = new SpelExpressionParser();
private BeanResolver br;
private RoleHierarchy roleHierarchy;
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;
}
//调用内部模板方法以创建StandardEvaluationContext和SecurityExpressionRoot对象。
public final EvaluationContext createEvaluationContext(Authentication authentication,
T invocation) {
SecurityExpressionOperations root = createSecurityExpressionRoot(authentication,
invocation);
StandardEvaluationContext ctx = createEvaluationContextInternal(authentication,
invocation);
ctx.setBeanResolver(br);
ctx.setRootObject(root);
return ctx;
}
//子类DefaultMethodSecurityExpressionHandler会重写
protected StandardEvaluationContext createEvaluationContextInternal(
Authentication authentication, T invocation) {
return new StandardEvaluationContext();
}
//实现以便为受支持的调用类型创建正确类型的根对象。
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);
}
}
createSecurityExpressionRoot()方法需要子类返回一个SecurityExpressionOperations实例,这个接口定义了许多与安全相关的方法。
public interface SecurityExpressionOperations {
Authentication getAuthentication();
boolean hasAuthority(String authority);
boolean hasAnyAuthority(String... authorities);
boolean hasRole(String role);
boolean hasAnyRole(String... roles);
boolean permitAll();
boolean denyAll();
boolean isAnonymous();
boolean isAuthenticated();
boolean isRememberMe();
boolean isFullyAuthenticated();
boolean hasPermission(Object target, Object permission);
boolean hasPermission(Object targetId, String targetType, Object permission);
}
SecurityExpressionRoot实现了SecurityExpressionOperations接口的所有方法。
public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
protected final Authentication authentication;
private AuthenticationTrustResolver trustResolver;
private RoleHierarchy roleHierarchy;
private Set roles;
private String defaultRolePrefix = "ROLE_";
/** Allows "permitAll" expression */
public final boolean permitAll = true;
/** Allows "denyAll" expression */
public final boolean denyAll = false;
private PermissionEvaluator permissionEvaluator;
public final String read = "read";
public final String write = "write";
public final String create = "create";
public final String delete = "delete";
public final String admin = "administration";
public SecurityExpressionRoot(Authentication authentication) {
if (authentication == null) {
throw new IllegalArgumentException("Authentication object cannot be null");
}
this.authentication = authentication;
}
public final boolean hasAuthority(String authority) {
return hasAnyAuthority(authority);
}
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
}
public final boolean hasRole(String role) {
return hasAnyRole(role);
}
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(defaultRolePrefix, roles);
}
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set roleSet = getAuthoritySet();
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
public final Authentication getAuthentication() {
return authentication;
}
public final boolean permitAll() {
return true;
}
public final boolean denyAll() {
return false;
}
public final boolean isAnonymous() {
return trustResolver.isAnonymous(authentication);
}
public final boolean isAuthenticated() {
return !isAnonymous();
}
public final boolean isRememberMe() {
return trustResolver.isRememberMe(authentication);
}
public final boolean isFullyAuthenticated() {
return !trustResolver.isAnonymous(authentication)
&& !trustResolver.isRememberMe(authentication);
}
public Object getPrincipal() {
return authentication.getPrincipal();
}
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
this.trustResolver = trustResolver;
}
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy;
}
//设置要添加到hasAnyRole(String ..))或hasRole(String)的默认前缀。
//例如,如果传入hasRole(“ADMIN”)或hasRole(“ROLE_ADMIN”),
//则当defaultRolePrefix为“ROLE_”(默认值)时,将使用角色ROLE_ADMIN。
//如果为null或为空,则不使用默认角色前缀。
public void setDefaultRolePrefix(String defaultRolePrefix) {
this.defaultRolePrefix = defaultRolePrefix;
}
private Set getAuthoritySet() {
if (roles == null) {
roles = new HashSet<>();
Collection extends GrantedAuthority> userAuthorities = authentication
.getAuthorities();
if (roleHierarchy != null) {
userAuthorities = roleHierarchy
.getReachableGrantedAuthorities(userAuthorities);
}
roles = AuthorityUtils.authorityListToSet(userAuthorities);
}
return roles;
}
public boolean hasPermission(Object target, Object permission) {
return permissionEvaluator.hasPermission(authentication, target, permission);
}
public boolean hasPermission(Object targetId, String targetType, Object permission) {
return permissionEvaluator.hasPermission(authentication, (Serializable) targetId,
targetType, permission);
}
public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
if (role == null) {
return role;
}
if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
return role;
}
if (role.startsWith(defaultRolePrefix)) {
return role;
}
return defaultRolePrefix + role;
}
}
从上面代码可以看出,通过SecurityExpressionHandler.createEvaluationContext()创建的EvaluationContext对象的根对象都是SecurityExpressionRoot的实现类,这样我们使用诸如hasAnyRole()、hasPermission()这样的表达式都会映射到SecurityExpressionRoot对象的方法用来完成权限检查。具体流程继续向后看。
AbstractSecurityExpressionHandler的实现类有两个:DefaultWebSecurityExpressionHandler、DefaultMethodSecurityExpressionHandler。
public class DefaultWebSecurityExpressionHandler extends
AbstractSecurityExpressionHandler implements
SecurityExpressionHandler {
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private String defaultRolePrefix = "ROLE_";
@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication, FilterInvocation fi) {
//比抽象类增加了一个hasIpAddress()方法,可用于按ip地址检查权限
WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(this.defaultRolePrefix);
return root;
}
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
}
public void setDefaultRolePrefix(String defaultRolePrefix) {
this.defaultRolePrefix = defaultRolePrefix;
}
}
对于这个问题在我之前的博客就说明了可以参考【Spring-Security源码分析】WebSecurity,这里我在简要复述一遍。
在使用WebSecurityConfigurerAdapter对HttpSecurity配置的时候,如果不重写下面这个方法其实现是这样的:
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
http.authorizeRequests()会使用ExpressionUrlAuthorizationConfigurer返回一个ExpressionInterceptUrlRegistry,anyRequest()方法将访问url与之HTTPf方法封装成RequestMatcher传入AuthorizedUrl中返回,对于anyRequest()来说这个RequestMatcher是AnyRequestMatcher不关心url是什么一切请求皆可匹配,对于指定url的请求可以参考使用antMatchers()方法。
public ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry authorizeRequests()
throws Exception {
ApplicationContext context = getContext();
return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
.getRegistry();
}
public C anyRequest() {
//AnyRequestMatcher.INSTANCE
return requestMatchers(ANY_REQUEST);
}
public C requestMatchers(RequestMatcher... requestMatchers) {
return chainRequestMatchers(Arrays.asList(requestMatchers));
}
protected final C chainRequestMatchers(List requestMatchers) {
this.unmappedMatchers = requestMatchers;
return chainRequestMatchersInternal(requestMatchers);
}
@Override
protected final AuthorizedUrl chainRequestMatchersInternal(
List requestMatchers) {
return new AuthorizedUrl(requestMatchers);
}
然后使用这个AuthorizedUrl为其对应的RequestMatcher分配需要的权限,AuthorizedUrl提供了多种方法如:authenticated()、hasAuthority()等等,这些分配权限的方法最终都会调用access()方法。
public ExpressionInterceptUrlRegistry hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
ExpressionUrlAuthorizationConfigurer#hasAuthority()方法
private static String hasAuthority(String authority) {
//其实最终就是调用跟对象的这个方法
return "hasAuthority('" + authority + "')";
}
//允许指定URL由任意表达式保护
public ExpressionInterceptUrlRegistry access(String attribute) {
if (not) {
attribute = "!" + attribute;
}
//SecurityConfig.createList(attribute)将表达式封装到SecurityConfig
interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
return ExpressionUrlAuthorizationConfigurer.this.REGISTRY;
}
//允许将多个RequestMatcher实例注册到ConfigAttribute实例的集合
private void interceptUrl(Iterable extends RequestMatcher> requestMatchers,
Collection configAttributes) {
for (RequestMatcher requestMatcher : requestMatchers) {
REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(
requestMatcher, configAttributes));
}
}
以上过程是配置方法url所需权限的过程。下面看ExpressionUrlAuthorizationConfigurer是如何配置HttpSecurity以应用上面的Mapping的。
@Override
public void configure(H http) throws Exception {
//要在FilterSecurityInterceptor上设置的FilterInvocationSecurityMetadataSource
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
if (metadataSource == null) {
return;
}
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
http, metadataSource, http.getSharedObject(AuthenticationManager.class));
if (filterSecurityInterceptorOncePerRequest != null) {
securityInterceptor
.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
}
securityInterceptor = postProcess(securityInterceptor);
http.addFilter(securityInterceptor);
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
FilterInvocationSecurityMetadataSource metadataSource,
AuthenticationManager authenticationManager) throws Exception {
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
securityInterceptor.setSecurityMetadataSource(metadataSource);
securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
securityInterceptor.setAuthenticationManager(authenticationManager);
securityInterceptor.afterPropertiesSet();
return securityInterceptor;
}
createMetadataSource()方法会返回一个ExpressionBasedFilterInvocationSecurityMetadataSource实例,在FilterSecurityInterceptor中进行权限检查时会用到此类的接口方法获取ConfigAttribute。
@Override
final ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource(
H http) {
LinkedHashMap> 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 getExpressionHandler(H http) {
if (expressionHandler == null) {
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) {
String[] roleHiearchyBeanNames = context.getBeanNamesForType(RoleHierarchy.class);
if (roleHiearchyBeanNames.length == 1) {
defaultHandler.setRoleHierarchy(context.getBean(roleHiearchyBeanNames[0], RoleHierarchy.class));
}
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaultsBeanNames.length == 1) {
GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
defaultHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
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;
}
getExpressionHandler()方法返回了一个上面讲述的DefaultWebSecurityExpressionHandler,这个对象会在ExpressionBasedFilterInvocationSecurityMetadataSource构造方法中使用。
public ExpressionBasedFilterInvocationSecurityMetadataSource(
LinkedHashMap> requestMap,
SecurityExpressionHandler expressionHandler) {
super(processMap(requestMap, expressionHandler.getExpressionParser()));
Assert.notNull(expressionHandler,
"A non-null SecurityExpressionHandler is required");
}
private static LinkedHashMap> processMap(
LinkedHashMap> requestMap,
ExpressionParser parser) {
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
LinkedHashMap> requestToExpressionAttributesMap = new LinkedHashMap>(
requestMap);
for (Map.Entry> entry : requestMap
.entrySet()) {
RequestMatcher request = entry.getKey();
Assert.isTrue(entry.getValue().size() == 1,
() -> "Expected a single expression attribute for " + request);
ArrayList attributes = new ArrayList<>(1);
String expression = entry.getValue().toArray(new ConfigAttribute[1])[0]
.getAttribute();
logger.debug("Adding web access control expression '" + expression + "', for "
+ request);
AbstractVariableEvaluationContextPostProcessor postProcessor = createPostProcessor(
request);
try {
attributes.add(new WebExpressionConfigAttribute(
parser.parseExpression(expression), postProcessor));
}
catch (ParseException e) {
throw new IllegalArgumentException(
"Failed to parse expression '" + expression + "'");
}
requestToExpressionAttributesMap.put(request, attributes);
}
return requestToExpressionAttributesMap;
}
processMap()方法主要将ExpressionInterceptUrlRegistry注册的SecurityConfig转换成WebExpressionConfigAttribute,这个WebExpressionConfigAttribute包含了使用SecurityExpressionHandler.getExpressionParser().parseExpression()返回的Expression的对象。
看到这里就知道了可以使用ExpressionBasedFilterInvocationSecurityMetadataSource获取对应RequestMatcher的WebExpressionConfigAttribute,而WebExpressionConfigAttribute又持有Expression对象,只要让DefaultWebSecurityExpressionHandler再给一个EvaluationContext就可以得出指定环境的表达式值了,而这个值的计算过程就是在AccessDecisionVoter的vote()方法中的,那么什么时候会调用这个方法呢?继续往下看!!!
从《【Spring-Security源码分析】WebSecurity》可知权限检查使用的是AccessDecisionManager的decide()方法,这里先看getAccessDecisionManager(http)的实现。
private AccessDecisionManager getAccessDecisionManager(H http) {
if (accessDecisionManager == null) {
accessDecisionManager = createDefaultAccessDecisionManager(http);
}
return accessDecisionManager;
}
private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
return postProcess(result);
}
@Override
@SuppressWarnings("rawtypes")
final List> getDecisionVoters(H http) {
List> decisionVoters = new ArrayList>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(getExpressionHandler(http));
decisionVoters.add(expressionVoter);
return decisionVoters;
}
在FilterSecurityInterceptor中,会使用accessDecisionManager尝试授权。
//这个具体实现只是轮询所有已配置的AccessDecisionVoters,并在任何AccessDecisionVoter肯定投票时授予访问权限。
//只有在拒绝投票且没有肯定投票的情况下才能拒绝访问。
//如果每个AccessDecisionVoter都弃权,则决定将基于isAllowIfAllAbstainDecisions()属性(默认为false)。
public void decide(Authentication authentication, Object object,
Collection configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
在前面代码已经知道ExpressionUrlAuthorizationConfigurer的getDecisionVoters()方法返回的是一个WebExpressionVoter的。
public class WebExpressionVoter implements AccessDecisionVoter {
private SecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
public int vote(Authentication authentication, FilterInvocation fi,
Collection attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {
return ACCESS_ABSTAIN;
}
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
fi);
ctx = weca.postProcess(ctx, fi);
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
}
private WebExpressionConfigAttribute findConfigAttribute(
Collection attributes) {
for (ConfigAttribute attribute : attributes) {
if (attribute instanceof WebExpressionConfigAttribute) {
return (WebExpressionConfigAttribute) attribute;
}
}
return null;
}
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof WebExpressionConfigAttribute;
}
public boolean supports(Class> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public void setExpressionHandler(
SecurityExpressionHandler expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
在上面代码vote()方法中,findConfigAttribute()方法返回当前请求对应的WebExpressionConfigAttribute,再使用SecurityExpressionHandler的createEvaluationContext()返回一个StandardEvaluationContext,接下来就是使用ExpressionUtils.evaluateAsBoolean()完成表达式求值了。
public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
try {
return ((Boolean) expr.getValue(ctx, Boolean.class)).booleanValue();
}
catch (EvaluationException e) {
throw new IllegalArgumentException("Failed to evaluate expression '"
+ expr.getExpressionString() + "'", e);
}
}
DefaultMethodSecurityExpressionHandler重写了AbstractSecurityExpressionHandler的createEvaluationContextInternal()和createSecurityExpressionRoot()方法。
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth,
MethodInvocation mi) {
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
}
MethodSecurityEvaluationContext增强了StandardEvaluationContext寻找变量的能力,如果在根对象找不到对应的属性或方法那么则以从执行方法的参数列表中寻找。
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication, MethodInvocation invocation) {
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(
authentication);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(getDefaultRolePrefix());
return root;
}
DefaultMethodSecurityExpressionHandler的使用是在使用Spring Security基于注解的权限检查时使用的,具体这里不再啰嗦,有兴趣的可以参考《Spring-Security源码分析】Spring Security基于注解认证原理》详细记录了它的实现原理。