本節給大家介紹Spring Security精確授權。用戶通過認證只是證明了訪問者得身法是否合法,但是并未確認其能訪問和可執行的動作。精確授權就是對其能夠進行哪些操作的細粒度的控制。
頁面級精確授權一般是指對頁面中的部分元素進行選擇性的顯示控制,而不是控制整個頁面。此外,頁面授權只是控制功能的顯示與不顯示,對于那些繞過界面直接訪問服務的行為,還需要方法授權加以保護。Spring Security為我們提供了頁面級和方法級兩種精確授權。
頁面授權主要是通過Jsp標簽庫在頁面添加條件聲明來實現
- 基于URL控制頁面元素
這種方式主要依據用戶權限配置的URL。我們在配置文件中配置了
.antMatchers("/admin/**").hasRole("ADMIN"),即只有具有ROLE_ADMIN的用戶才能訪問/admin及其下級頁面。當使用以下標簽時,Spring Security會根據該配置控制元素的顯示。
<sec:authorize url="/admin">
<h1>通過Url控制訪問:</h1>
<c:out value="/admin"/>
</sec:authorize>
當用戶具備能夠訪問對應URL的角色時,標簽內部的元素就會顯示。
- 基于Spring EL控制頁面元素
當我們沒有配置URL與角色關系時,我們可以使用Spring EL表達式控制頁面元素的顯示。
<sec:authorize access="hasRole('ROLE_ADMIN') and fullyAuthenticated">
<h1>通過Spring EL控制訪問:</h1>
<c:out value="hasRole('ROLE_ADMIN') and fullyAuthenticated"/>
</sec:authorize>
當用戶具備Spring EL描述的角色時,標簽內部的元素就會顯示。
方法授權主要是通過Annotation或者Java Configuration來實現
- Annotation
在要控制訪問的方法上添加注解就可以實現訪問控制,前提是方法所在的實例應是Spring的bean。
在@Configuration之后增加@EnableGlobalMethodSecurity(prePostEnabled = true)注解以開啟方法保護,其中prePostEnabled = true標識允許使用@PreAuthorize()注解。在UserService的preAuthorizeMethod方法標記注解,限制只有具備ROLE_ADMIN的用戶才能訪問該方法,否則系統拋出AccessDeniedException。
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String preAuthorizeMethod() {
return "This method is protected by Spring Security Annotation.";
}
通過使用@PreFilter還可以控制集合的元素
@PostFilter("((filterObject.contains('2') or filterObject.contains('4')) and hasRole('ROLE_USER')) or hasRole('ROLE_ADMIN')")
public List<String> postFilter() {
List<String> list = new ArrayList<>();
for (int index = 0; index < 10; index++) {
list.add("Item" + index);
}
return list;
}
ROLE_USER角色只能訪問集合中的部分元素,而ROLE_ADMIN可以訪問全部元素
- Java Configuration
使用Annotation需要我們在每個需要控制的方法上都要增加注解,這樣做非常繁雜且不易維護。Spring Security為我們提供了通過Java Configuration的方式控制方法訪問的渠道。那么我們就可以通過數據庫等方式動態訪問權限,簡化控制設置。為了實現Java Configuration的方式我們需要多做一些工作。
創建一個新的類繼承GlobalMethodSecurityConfiguration,并且標記@EnableGlobalMethodSecurity。重寫方法customMethodSecurityMetadataSource就可以實現對于任意類的任意方法的精確授權。
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
MapBasedMethodSecurityMetadataSource metadataSource = new MapBasedMethodSecurityMetadataSource();
metadataSource.addSecureMethod(UserService.class, "javaConfiguredMethod", SecurityConfig.createList("ROLE_ADMIN"));
return metadataSource;
}
該方法至此靜態授權,即系統初始化時就確定好方法的訪問權限,初始化后不可再修改。查看源代碼發現,所有的方法授權都存儲在一個Spring bean中,DelegatingMethodSecurityMetadataSource包含所有的靜態授權權限。
@Bean
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
List<MethodSecurityMetadataSource> sources = new ArrayList<MethodSecurityMetadataSource>();
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
getExpressionHandler());
MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
if (customMethodSecurityMetadataSource != null) {
sources.add(customMethodSecurityMetadataSource);
}
if (prePostEnabled()) {
sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory));
}
if (securedEnabled()) {
sources.add(new SecuredAnnotationSecurityMetadataSource());
}
if (jsr250Enabled()) {
GrantedAuthorityDefaults grantedAuthorityDefaults =
getSingleBeanOrNull(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaults != null) {
this.jsr250MethodSecurityMetadataSource.setDefaultRolePrefix(
grantedAuthorityDefaults.getRolePrefix());
}
sources.add(jsr250MethodSecurityMetadataSource);
}
return new DelegatingMethodSecurityMetadataSource(sources);
}
如果希望實現動態的授權,可以重新定義該bean("methodSecurityMetadataSource"),擴展MethodSecurityMetadataSource,增加通過數據庫等方式獲取方法授權的信息即可。
代碼示例:https://github.com/wexgundam/spring.security/tree/master/ch11