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());
}
"/??/??/??/** " 不要漏掉 / **