訪問(wèn)控制表達(dá)式
1. 前言
在實(shí)際的開(kāi)發(fā)過(guò)程中,權(quán)限的分配規(guī)則往往非常復(fù)雜,如果才能實(shí)現(xiàn)快速實(shí)現(xiàn)細(xì)粒度的權(quán)限策略呢?Spring Security 已為我們找到了解決辦法。
自從 Spring Security 3.0 支持了使用 Spring 語(yǔ)法表達(dá)式來(lái)配置安全規(guī)則,便大大降低了安全規(guī)則實(shí)現(xiàn)的復(fù)雜度。
本節(jié),我們主要討論如何通過(guò) Spring Security 訪問(wèn)控制表達(dá)式實(shí)現(xiàn)安全規(guī)則的配置。
2.Spring 內(nèi)置表達(dá)式
Spring Security 使用 Spring EL (Spring 表達(dá)式語(yǔ)法)用來(lái)支持表達(dá)式配置。表達(dá)式是作為運(yùn)算上下文中的根級(jí)對(duì)象被執(zhí)行的。
SecurityExpressionRoot 是支持表達(dá)式的基礎(chǔ)實(shí)現(xiàn)類,它提供了一些支持 Web 或者方法層面的安全表達(dá)式。

以下為其支持的表達(dá)式:
hasRole(String role)
如果當(dāng)前的用戶身份信息中,包含 role 值的角色時(shí),該表達(dá)式返回 true。
例如判斷是否具有 admin 角色:hasRole('admin')。
需要注意的是,角色名稱在 Spring Security 內(nèi)會(huì)自動(dòng)增加 ROLE_ 前綴,如果需要修改該前綴,可通過(guò) DefaultWebSecurityExpressionHandler 對(duì)象中的 defaultRolePrefix 屬性實(shí)現(xiàn)。
hasAnyRole(String… roles)
和 hasRole 類似,可以同時(shí)判斷多個(gè)角色,只要包含其中一種即可,多個(gè)角色用逗號(hào)隔開(kāi)。
hasAuthority(String authority)
如果當(dāng)前的身份信息中包含參數(shù)中指定權(quán)限,則返回 true。
例如:hasAuthority('read')
hasAnyAuthority(String… authorities)
如果當(dāng)前的身份信息中包含參數(shù)中指定權(quán)限之一,則返回 true。多個(gè)權(quán)限之間用逗號(hào) , 分隔。
例如:hasAnyAuthority('read', 'write')
principal
允許當(dāng)前登錄用戶直接訪問(wèn)其身份信息 principal 對(duì)象。
authentication
允許直接訪問(wèn)當(dāng)前安全上下文中的認(rèn)證信息 Authentication 對(duì)象。
permitAll
永遠(yuǎn)返回 true。
denyAll
永遠(yuǎn)返回 false。
isAnonymous()
如果當(dāng)前用戶的身份信息為匿名用戶,則返回 true。
isRememberMe()
如果當(dāng)前用戶的身份信息是來(lái)自于「記住我」認(rèn)證用戶,則返回 true。
isAuthenticated()
如果當(dāng)前用戶的身份信息不是匿名用戶,則返回 true。
isFullyAuthenticated()
如果當(dāng)前用戶的身份信息既不是匿名用戶又不是記住我自動(dòng)登錄用戶,則返回 true。
hasPermission(Object target, Object permission)
如果當(dāng)前用戶包含對(duì)指定對(duì)象的訪問(wèn)權(quán)限,則返回 true。
例如:hasPermission(domainObject, 'read')。
hasPermission(Object targetId, String targetType, Object permission)
如果當(dāng)前用戶包含對(duì)指定對(duì)象的訪問(wèn)權(quán)限,則返回 true。
例如:hasPermission(1, 'com.example.domain.Message', 'read')。
3. 內(nèi)置表達(dá)式在 Web 系統(tǒng)中的使用
3.1 表達(dá)式的應(yīng)用配置
要針對(duì) URL 應(yīng)用表達(dá)式規(guī)則,我們需要在 <http> 對(duì)象上將 use-expressions 屬性值置為 true。Spring Security 則會(huì)將 <intercept-url> 元素中 access 屬性值解釋為表達(dá)式,并根據(jù)表達(dá)式規(guī)則返回 true 或者 false 的判定結(jié)果。
例如:
<http>
<intercept-url pattern="/admin*"
access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
...
</http>
本例中,我們將 /admin 開(kāi)頭的資源定義為只有包含 admin 角色,并且來(lái)源 IP 為 192.168.1.x 網(wǎng)段的用戶才可以訪問(wèn)。這里出現(xiàn)的 hasIpAddress 方法在前面內(nèi)容中沒(méi)有提到,因?yàn)樗侵挥?Web 系統(tǒng)才包含的,被定義在 WebSecurityExpressionRoot 類中。
3.2 使用自定義方法作為表達(dá)式
如果我們需要擴(kuò)展現(xiàn)有的表達(dá)式,我們可以使用 Spring Bean 里的方法。
例如,使用自定義 Bean WebSecurity 中的 check 方法:
public class WebSecurity {
public boolean check(Authentication authentication, HttpServletRequest request) {
...
}
}
我們可以在配置文件中這樣寫:
<http>
<intercept-url pattern="/user/**"
access="@webSecurity.check(authentication,request)"/>
...
</http>
或者在 Java 中直接加入配置:
http
.authorizeRequests(authorize -> authorize
.antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
...
)
3.3 路徑參數(shù)的使用
很多時(shí)候我們的路徑是包含參數(shù)信息的,而規(guī)則的應(yīng)用往往也和參數(shù)信息想匹配。例如針對(duì)用戶信息的 RESTful 資源地址 /user/{userId}。我們可以在表達(dá)式中使用這些參數(shù)。
例如,假設(shè)我們的擴(kuò)展表達(dá)式方法如下:
public class WebSecurity {
public boolean checkUserId(Authentication authentication, int id) {
...
}
}
此方法的 id 參數(shù)需要傳入路徑中的 userId 的值,那我們?cè)谂渲帽磉_(dá)式時(shí)可用如下方式:
<http>
<intercept-url pattern="/user/{userId}/**"
access="@webSecurity.checkUserId(authentication,#userId)"/>
...
</http>
或者用 Java 的方式配置
http
.authorizeRequests(authorize -> authorize
.antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
...
);
上述寫法的結(jié)果都是將 userId 作為參數(shù)傳入了方法之中,加入用戶訪問(wèn)的 URL 地址為 /user/123/resource,則傳入的 id 參數(shù)為 123。
4. 方法安全表達(dá)式
方法安全表達(dá)式通常通過(guò)注解方式控制方法的訪問(wèn)。這里主要使用了 @Pre 和 @Post 注解。
@Pre 代表執(zhí)行前,@Post 代表執(zhí)行后。在方法的安全表達(dá)式中,有四個(gè)相關(guān)的表達(dá)式注解,分別是:@PreAuthorize,@PreFilter,@PostAuthorize 和 @PostFilter。要啟用這四個(gè)標(biāo)簽要,首先要增加全局配置:
<global-method-security pre-post-annotations="enabled"/>
4.1 使用 @PreAuthorize 和 @PostAuthorize 進(jìn)行訪問(wèn)控制
@PreAuthorize 最主要的作用是決定一個(gè)方法能否被執(zhí)行。例如:
@PreAuthorize("hasRole('USER')")
public void create(Contact contact);
這段代碼的含義是,只有包含了 USER 角色的用戶,才被允許調(diào)用 create 方法。
對(duì)于權(quán)限,還有另一種更為具體的寫法:
@PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);
這里我們指定了調(diào)用方法中的參數(shù),用來(lái)判斷當(dāng)前用戶是否對(duì)將被刪除的 Contact 對(duì)象具有 admin 角色。通過(guò)這種寫法,我們可以將目標(biāo)對(duì)象中的任意方法作為表達(dá)式的變量參數(shù)。
Spring Security 要在表達(dá)式中訪問(wèn)方法參數(shù)有幾種方式:
- Spring Security 可以使用
@P注解,為變量設(shè)置別名。例如:
import org.springframework.security.access.method.P;
...
@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);
- 使用 Spring 的
@Param注解,為變量設(shè)置別名。例如:
import org.springframework.data.repository.query.Param;
...
@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);
- 如果使用的是 JDK 8 + Spring 4+ ,不需要額外的注解,程序會(huì)自動(dòng)發(fā)現(xiàn)參數(shù)名稱。
Spring Security 的表達(dá)式注解支持對(duì)象的屬性。例如,當(dāng)我們指定方法入?yún)⒅械膶傩悦Q和我們身份信息中的名稱一致時(shí)允許訪問(wèn):
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
這里我們用到了內(nèi)置參數(shù) authentication ,該對(duì)象保存在 Spring Security 的安全上下文中。我們也可以直接訪問(wèn)到該對(duì)象的身份信息屬性。
@PostAuthorize 使用場(chǎng)景相對(duì)較少,它針對(duì)方法的返回進(jìn)行權(quán)限過(guò)濾。
4.2 使用 @PreFilter 和 @PostFilter
@PreFilter 過(guò)濾器用于處理參數(shù)中的集合或者數(shù)組對(duì)象;@PostFilter 用于過(guò)濾返回值中集合或者數(shù)組對(duì)象。
例如:
@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();
5. 小結(jié)
本節(jié)討論了 Spring Security 中如何通過(guò)表達(dá)式的方式簡(jiǎn)化權(quán)限規(guī)則配置,主要內(nèi)容有:
- 表達(dá)式方式簡(jiǎn)化了鑒權(quán)配置難度,增強(qiáng)鑒權(quán)規(guī)則的可讀性;
- Spring 內(nèi)置了非常多的表達(dá)式,可以滿足大多數(shù)場(chǎng)景;
- 表達(dá)式是對(duì)實(shí)例方法的引用,我們可以擴(kuò)展自己的表達(dá)式鑒權(quán)方法;
- @PreAuthorize 和 @PostAuthorize 分別代表在方法調(diào)用前或方法返回前執(zhí)行表達(dá)式鑒權(quán);
- @PreFilter 和 @PostFilter 是對(duì)入?yún)⒒蛘叻祷刂档臈l件過(guò)濾。
下節(jié)我們討論一個(gè)新的概念「安全對(duì)象」。
童雷 ·
2025 imooc.com All Rights Reserved |