實際使用中,資源都是保存在數據庫中,而不是在XML中進行配置。
使用數據庫管理資源時,需要實現FilterInvocationSecurityMetadataSource接口:
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource, InitializingBean {
private boolean expire = false;
private final static List<ConfigAttribute> NULL_CONFIG_ATTRIBUTE = Collections.emptyList();
private Map<String, Collection<ConfigAttribute>> requestMap;
private AntPathMatcher urlMatcher = new AntPathMatcher();
@Autowired
private SysResourceService sysResourceService;
/**
* 獲取所有權限集合
*/
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> set = new HashSet<ConfigAttribute>();
for (Map.Entry<String, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
set.addAll(entry.getValue());
}
return set;
}
/**
* 根據request請求獲取訪問資源所需權限
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object obj) throws IllegalArgumentException {
//刷新資源
if(isExpire()){
this.requestMap.clear();
expire = false;
}
//若map爲空,則重新加載
if(this.requestMap==null || this.requestMap.isEmpty()){
this.requestMap = bindRequestMap();
}
String URL = ((FilterInvocation) obj).getRequestUrl();
if (URL.contains("&")) {
URL = URL.substring(0, URL.indexOf("&"));
}
Collection<ConfigAttribute> attrs = NULL_CONFIG_ATTRIBUTE;
for (Map.Entry<String, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
if (urlMatcher.match(URL, entry.getKey())) {
attrs = entry.getValue();
break;
}
}
return attrs;
}
@Override
public boolean supports(Class<?> arg0) {
return FilterInvocation.class.isAssignableFrom(arg0);
}
@Override
public void afterPropertiesSet() throws Exception {
this.requestMap = this.bindRequestMap();
}
protected Map<String, Collection<ConfigAttribute>> bindRequestMap() {
Map<String, Collection<ConfigAttribute>> map = new LinkedHashMap<String, Collection<ConfigAttribute>>();
Map<String, String> sourceMap = this.loadResouece();
for (Map.Entry<String, String> entry : sourceMap.entrySet()) {
String key = entry.getKey();
Collection<ConfigAttribute> attr = new ArrayList<ConfigAttribute>();
attr = SecurityConfig.createListFromCommaDelimitedString(entry.getValue());
map.put(key, attr);
}
return map;
}
/**
* 從數據庫中加載權限和資源的對應列表
*
* @return
*/
private Map<String, String> loadResouece() {
Map<String, String> map = new LinkedHashMap<String, String>();
List<SysResource> list = sysResourceService.getAllResRole();
if (list != null && list.size() > 0) {
for (SysResource r : list) {
String path = r.getResourcePath();
String code = r.getGroupCode();
if (map.containsKey(path)) {
String existCode = map.get(path);
map.put(path, existCode + "," + code);
} else {
map.put(path, code);
}
}
}
return map;
}
public boolean isExpire() {
return expire;
}
public void expireNow(){
this.expire = true;
}
}
以上代碼主要的做法就是:
(1)將資源從數據庫中查出來,並和其對應的角色做關聯,封裝到Map<String, Collection<ConfigAttribute>>中;
(2)每次請求到達過濾器後,根據請求的URL去map中匹配對應的權限(即角色)。
在security的配置文件中做相應配置:自己實現的過濾器要在security的之前執行
<!-- 自定義過濾器,在security的過濾器之前執行 -->
<custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
</pre><pre name="code" class="html"><beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<beans:property name="securityMetadataSource" ref="securityMetadataSource"></beans:property>
<beans:property name="accessDecisionManager" ref="accessDecisionManager"></beans:property>
<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
</beans:bean>
以上步驟完成後,就可以實現對資源URL的過濾。
在實際使用時,還需要注意到的一個問題就是,在每次分配完權限之後,需重啓系統才能生效。這是因爲該類初始化之後將資源map加載到了內存中,數據庫發生了變化,但內存並沒有刷新。
基於以上原理,解決該問題的辦法就是:在每次分配完權限之後,重新加載map,這也是代碼中加入expire屬性的原因。
// 數據庫權限發生變化,需要刷新內存
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(request
.getServletContext());
MySecurityMetadataSource source = (MySecurityMetadataSource)wac.getBean("securityMetadataSource");
source.expireNow();
這樣可以做到即使不用LogOut更不用重啓項目,也可以動態的刷新權限資源。