SecureResourceFilterInvocationDefinitionSource部分
/**
* RegexUrlPathMatcher默认不进行小写转换,而AntUrlPathMatcher默认要进行小写转换
*/
public void afterPropertiesSet() throws Exception {
// default url matcher will be RegexUrlPathMatcher
this.urlMatcher = new RegexUrlPathMatcher();
if (useAntPath) { // change the implementation if required
this.urlMatcher = new AntUrlPathMatcher();
}
// Only change from the defaults if the attribute has been set
if ("true".equals(lowercaseComparisons)) {
if (!this.useAntPath) {
((RegexUrlPathMatcher) this.urlMatcher).setRequiresLowerCaseUrl(true);
}
} else if ("false".equals(lowercaseComparisons)) {
if (this.useAntPath) {
//是否对URL全部转换成小写格式
((AntUrlPathMatcher) this.urlMatcher).setRequiresLowerCaseUrl(false);
}
}
}
//这个方法主要会在FilterSecurityInterceptor->AbstractSecurityInterceptor->beforeInvocation中用到
public ConfigAttributeDefinition getAttributes(Object filter) throws IllegalArgumentException {
FilterInvocation filterInvocation = (FilterInvocation) filter;
String requestURI = filterInvocation.getRequestUrl();
Map<String, String> urlAuthorities = this.getUrlAuthorities(filterInvocation);
String grantedAuthorities = null;
for(Iterator<Map.Entry<String, String>> iter = urlAuthorities.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String, String> entry = iter.next();
//url表示从资源表取出的值,在这里代表的是相应的URL
String url = entry.getKey();
//这段代码表示数据库内的需要验证的资源URL与当前请求的URL相匹配时进行验证
if(urlMatcher.pathMatchesUrl(url, requestURI)) {
//grantedAuthorities表示每个资源对应的角色,如果有多个角色,则以','隔开
grantedAuthorities = entry.getValue();
break;
}
}
if(grantedAuthorities != null) {
ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
configAttrEditor.setAsText(grantedAuthorities);
return (ConfigAttributeDefinition) configAttrEditor.getValue();
}
//返回null表示不会验证
return null;
}
这个方法的主要作用是从数据库的resource表加载出所有的资源URL值,通过它与request请求的URL相比较,而比较器一般采用AntUrlPathMatcher,如果需要更加灵活的方式,可以使用RegexUrlPathMatcher,先介绍一下上面的getAttributes是怎么通过URL进行验证的。从上面的for循环可以看出,首先遍历资源URL的列表,如果发现有与当前request请求URL相匹配的,就进行验证,如果当前用户拥有的角色与此资源URL对应的角色相同,则通过验证,否则禁止访问。见下图:
上面一张简单的图,基本能说明意思,需要说明的是只要发现有与当前请求路径相匹配的,就会进行验证,而且只验证一次,没有通过验证也不会再检查是否匹配第二个资源URL,的当然如果请求的URL与第一个资源URL不匹配,就会继续向下查找,直到找到与请求URL匹配的资源URL为止,如果遍历完以后,还是没有查到,就到弃权处理,至于所有投票者都弃权以后,该怎么处理,前一篇博客有介绍的。由此可知,这个资源的URL路径的顺序就比较重要了。如果遍历出来的第一个资源URL为/**,而普通用户角色对应的资源URL没有它,那么即使普通角色拥有其它的资源URL权限也是不能访问到相应的页面的。但是如果普通用户有/**的权限,而/**是匹配所有路径的,如/admin/index.jsp也是会匹配的,这样就相当于普通用户拥有任何路径的权限,这也是不行的。所以遍历出来的资源URL顺序很重要。
在基于XML的方式授权时就是把/**放在最后面的,如:
<intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR"/>
<intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_REMEMBERED" />
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
也就是说,这个路径放的顺序很重要,在数据库里存放URL时也要遵循这样的规则,如上面的XML放在数据库里应该这样:
这里需要多做的一个步骤是,取出来后不要改变取数据库记录的顺序。在SecurityManagerSupport中的代码:
public Map<String, String> loadUrlAuthorities() {
Map<String, String> urlAuthorities = new LinkedHashMap<String, String>();
@SuppressWarnings("unchecked")
List<Resource> urlResources = getHibernateTemplate().find("FROM Resource resource WHERE resource.type = ?", "URL");
for(Resource resource : urlResources) {
urlAuthorities.put(resource.getValue(), resource.getRoleAuthorities());
}
return urlAuthorities;
}
要保持取出的数据的顺序不改变,需要使用LinkedHashMap,这样SecureResourceFilterInvocationDefinitionSource在验证URL的顺序就与数据库里面存的顺序一致了,表面上看的确有些不灵活,而实际上,在使用中时资源URL是固定的,用户不能改变的,所以这也没什么影响。
关于AntUrlPathMatcher的匹配规则也很简单,文档中有说明:
* <li>? matches one character</li>
* <li>* matches zero or more characters</li>
* <li>** matches zero or more 'directories' in a path</li>
//
//
//
//
Spring Security通过URL模式匹配的声明式权限控制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.GET, "/??/??/??/**").permitAll() //不验证token
.anyRequest().authenticated()
.and()
//.addFilter(new JWTLoginFilter(authenticationManager()))
.addFilter(jwtAuthenticationFilterBean());
}
"/??/??/??/** " 不要漏掉 / **