SpringSecurity授權(quán)機制的實現(xiàn)(AccessDecisionManager與投票決策)
引言
在企業(yè)級應(yīng)用開發(fā)中,安全控制不僅包括認證(Authentication)——確認用戶身份,還包括授權(quán)(Authorization)——確定用戶是否有權(quán)執(zhí)行特定操作。Spring Security提供了一套精心設(shè)計的授權(quán)機制,其核心是AccessDecisionManager和投票系統(tǒng)。與簡單的角色檢查相比,這種機制提供了更細粒度、更靈活的訪問控制能力。本文將深入探討Spring Security授權(quán)框架的內(nèi)部工作原理,重點分析AccessDecisionManager如何通過投票機制做出授權(quán)決策,以及如何根據(jù)業(yè)務(wù)需求進行定制化配置。通過這些知識,開發(fā)者可以構(gòu)建既安全又靈活的訪問控制系統(tǒng)。
一、Spring Security授權(quán)架構(gòu)
Spring Security的授權(quán)架構(gòu)采用了責(zé)任鏈和投票模式相結(jié)合的設(shè)計,使授權(quán)決策過程模塊化且可擴展。授權(quán)過程始于SecurityFilterChain中的FilterSecurityInterceptor,它攔截受保護資源的請求,收集安全元數(shù)據(jù)(如所需權(quán)限),然后委托給AccessDecisionManager進行授權(quán)判斷。AccessDecisionManager通過組合多個AccessDecisionVoter實現(xiàn)復(fù)雜的授權(quán)策略,每個投票者根據(jù)自己的邏輯對授權(quán)請求投贊成、反對或棄權(quán)票。這種分層設(shè)計使得授權(quán)邏輯與業(yè)務(wù)代碼完全分離,便于維護和擴展。
// FilterSecurityInterceptor 關(guān)鍵部分
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor {
private final FilterInvocationSecurityMetadataSource securityMetadataSource;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 封裝HTTP請求
FilterInvocation fi = new FilterInvocation(request, response, chain);
// 進行安全攔截
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
// 檢查安全攔截器是否應(yīng)該被應(yīng)用
if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)) {
// 安全攔截器已應(yīng)用,繼續(xù)執(zhí)行過濾器鏈
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
// 標記攔截器已應(yīng)用
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
// 執(zhí)行安全攔截
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 繼續(xù)執(zhí)行過濾器鏈
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
// 清理安全上下文
super.finallyInvocation(token);
}
// 執(zhí)行后處理
super.afterInvocation(token, null);
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
二、AccessDecisionManager接口設(shè)計
AccessDecisionManager是Spring Security授權(quán)體系的核心接口,負責(zé)協(xié)調(diào)多個AccessDecisionVoter并做出最終授權(quán)決策。它定義了三個關(guān)鍵方法:decide()執(zhí)行授權(quán)判斷,supports(ConfigAttribute)檢查是否支持特定配置屬性,supports(Class)檢查是否支持特定安全對象類型。通過這些方法,AccessDecisionManager可以靈活處理不同類型的授權(quán)請求,如Web請求、方法調(diào)用或特定領(lǐng)域?qū)ο蟮脑L問。Spring Security提供了三種內(nèi)置實現(xiàn):AffirmativeBased(只要有一票贊成即通過)、ConsensusBased(多數(shù)票決定)和UnanimousBased(要求全票通過)。
// AccessDecisionManager接口定義
public interface AccessDecisionManager {
/**
* 對給定的安全對象做出訪問控制決策
* @param authentication 當前用戶的認證信息
* @param object 要訪問的安全對象
* @param configAttributes 安全對象的安全配置屬性
* @throws AccessDeniedException 如果拒絕訪問
* @throws InsufficientAuthenticationException 如果認證不足
*/
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException;
/**
* 檢查此AccessDecisionManager是否支持指定的ConfigAttribute
* @param attribute 要檢查的配置屬性
* @return 如果支持該屬性返回true
*/
boolean supports(ConfigAttribute attribute);
/**
* 檢查此AccessDecisionManager是否支持指定的安全對象類型
* @param clazz 安全對象的類型
* @return 如果支持該類型返回true
*/
boolean supports(Class<?> clazz);
}
三、投票決策實現(xiàn)
Spring Security提供了三種不同的AccessDecisionManager實現(xiàn),每種實現(xiàn)代表不同的投票策略。AffirmativeBased采用"一票通過"策略,只要有一個投票者投贊成票就允許訪問,這是最寬松的策略。ConsensusBased基于"多數(shù)票"原則,根據(jù)贊成票與反對票的比較結(jié)果做出決策。UnanimousBased要求所有投票者都投贊成票(或棄權(quán))才允許訪問,是最嚴格的策略。這三種實現(xiàn)覆蓋了從寬松到嚴格的不同安全需求,開發(fā)者可以根據(jù)業(yè)務(wù)場景選擇合適的策略。
// AffirmativeBased實現(xiàn)關(guān)鍵代碼
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes)
throws AccessDeniedException {
int deny = 0;
// 遍歷所有投票者
for (AccessDecisionVoter voter : getDecisionVoters()) {
// 獲取投票結(jié)果
int result = voter.vote(authentication, object, attributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED: // 投票通過
return; // 立即返回允許訪問
case AccessDecisionVoter.ACCESS_DENIED: // 投票拒絕
deny++; // 計數(shù)拒絕票
break;
default: // 棄權(quán)
break;
}
}
// 如果有拒絕票且沒有通過票,則拒絕訪問
if (deny > 0) {
throw new AccessDeniedException("訪問被拒絕,安全投票失敗");
}
// 所有投票者棄權(quán),根據(jù)配置決定(默認允許)
if (getAllowIfAllAbstainDecisions()) {
return;
}
// 默認拒絕訪問
throw new AccessDeniedException("訪問被拒絕,沒有投票者同意");
}
}
四、AccessDecisionVoter機制
AccessDecisionVoter是Spring Security授權(quán)機制中的投票者角色,負責(zé)對特定授權(quán)請求投票。每個投票者實現(xiàn)vote()方法,返回ACCESS_GRANTED(贊成)、ACCESS_DENIED(反對)或ACCESS_ABSTAIN(棄權(quán))。常用的投票者包括:RoleVoter(基于角色投票)、AuthenticatedVoter(基于認證狀態(tài)投票)和WebExpressionVoter(基于SpEL表達式投票)。投票者的靈活性在于它可以基于任何條件做出決策,不僅限于用戶角色,還可以考慮時間、位置、資源屬性等因素。
// AccessDecisionVoter接口
public interface AccessDecisionVoter<S> {
/**
* 贊成訪問的常量
*/
int ACCESS_GRANTED = 1;
/**
* 拒絕訪問的常量
*/
int ACCESS_DENIED = -1;
/**
* 棄權(quán)的常量
*/
int ACCESS_ABSTAIN = 0;
/**
* 對訪問請求進行投票
* @param authentication 當前用戶的認證信息
* @param object 要訪問的安全對象
* @param attributes 安全對象的配置屬性
* @return 投票結(jié)果:ACCESS_GRANTED、ACCESS_DENIED或ACCESS_ABSTAIN
*/
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
/**
* 檢查此投票者是否支持指定的配置屬性
* @param attribute 要檢查的配置屬性
* @return 如果支持該屬性返回true
*/
boolean supports(ConfigAttribute attribute);
/**
* 檢查此投票者是否支持指定的安全對象類型
* @param clazz 安全對象的類型
* @return 如果支持該類型返回true
*/
boolean supports(Class<?> clazz);
}
// RoleVoter實現(xiàn)
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
// 如果沒有屬性,棄權(quán)
if (attributes.isEmpty()) {
return ACCESS_ABSTAIN;
}
// 獲取用戶權(quán)限
Collection<? extends GrantedAuthority> authorities =
authentication.getAuthorities();
// 檢查每個配置屬性
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
// 屬性值作為需要的角色
String role = attribute.getAttribute();
// 檢查用戶是否擁有該角色
for (GrantedAuthority authority : authorities) {
if (role.equals(authority.getAuthority())) {
return ACCESS_GRANTED; // 用戶有所需角色,允許訪問
}
}
// 走到這里說明用戶沒有所需角色,返回拒絕
return ACCESS_DENIED;
}
}
// 沒有可支持的屬性,棄權(quán)
return ACCESS_ABSTAIN;
}
@Override
public boolean supports(ConfigAttribute attribute) {
// 檢查屬性是否以角色前綴開頭
return (attribute.getAttribute() != null) &&
attribute.getAttribute().startsWith(rolePrefix);
}
@Override
public boolean supports(Class<?> clazz) {
return true; // 支持所有類型的安全對象
}
}
五、自定義訪問控制規(guī)則
Spring Security的授權(quán)機制最大的優(yōu)勢在于其可擴展性,開發(fā)者可以輕松實現(xiàn)自定義的訪問控制規(guī)則。通過創(chuàng)建自定義的AccessDecisionVoter,可以基于業(yè)務(wù)特定邏輯進行授權(quán)決策,如限制特定時間段的訪問、根據(jù)用戶屬性控制權(quán)限或?qū)崿F(xiàn)數(shù)據(jù)行級安全。自定義投票者只需實現(xiàn)AccessDecisionVoter接口的三個方法,然后將其添加到AccessDecisionManager的投票者列表中即可。這種方式使復(fù)雜的授權(quán)需求變得易于實現(xiàn)且可維護。
// 自定義的工作時間投票者,只允許在工作時間訪問
public class BusinessHoursVoter implements AccessDecisionVoter<Object> {
private final int startHour = 9; // 工作開始時間
private final int endHour = 17; // 工作結(jié)束時間
@Override
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
// 檢查是否有工作時間限制的屬性
boolean businessHoursRequired = attributes.stream()
.anyMatch(a -> "BUSINESS_HOURS_ONLY".equals(a.getAttribute()));
// 如果沒有時間限制,棄權(quán)
if (!businessHoursRequired) {
return ACCESS_ABSTAIN;
}
// 獲取當前時間
LocalTime now = LocalTime.now();
int currentHour = now.getHour();
// 檢查是否在工作時間內(nèi)
if (currentHour >= startHour && currentHour < endHour) {
return ACCESS_GRANTED;
} else {
return ACCESS_DENIED;
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute != null && "BUSINESS_HOURS_ONLY".equals(attribute.getAttribute());
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
// 自定義配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").access("hasRole('ADMIN') and hasAuthority('BUSINESS_HOURS_ONLY')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll();
// 替換默認的AccessDecisionManager
http.authorizeRequests()
.accessDecisionManager(accessDecisionManager());
}
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> voters = new ArrayList<>();
voters.add(new WebExpressionVoter());
voters.add(new RoleVoter());
voters.add(new AuthenticatedVoter());
voters.add(new BusinessHoursVoter()); // 添加自定義投票者
// 使用"一票通過"策略
return new AffirmativeBased(voters);
}
}
六、方法級安全控制
除了Web請求的授權(quán)控制外,Spring Security還提供了方法級別的安全控制,使開發(fā)者能夠直接在業(yè)務(wù)方法上應(yīng)用授權(quán)規(guī)則。通過@PreAuthorize、@PostAuthorize等注解,可以使用SpEL表達式定義復(fù)雜的訪問條件。這些注解由MethodSecurityInterceptor處理,它同樣使用AccessDecisionManager進行授權(quán)決策。方法級安全與Web安全共享相同的授權(quán)架構(gòu),但提供了更精細的控制粒度,特別適合業(yè)務(wù)邏輯層的權(quán)限管理。
// 啟用方法級安全
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
// 自定義方法級安全的AccessDecisionManager
AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager();
// 獲取現(xiàn)有的投票者并添加自定義投票者
List<AccessDecisionVoter<?>> voters = new ArrayList<>(
accessDecisionManager.getDecisionVoters());
voters.add(new BusinessHoursVoter());
// 創(chuàng)建新的AccessDecisionManager
return new AffirmativeBased(voters);
}
}
// 在服務(wù)類中使用方法級安全
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN') and hasAuthority('BUSINESS_HOURS_ONLY')")
public void deleteUser(Long userId) {
// 刪除用戶的邏輯
}
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public UserDetails viewUserProfile(Long userId) {
// 查看用戶資料的邏輯
return userProfile;
}
@PostAuthorize("returnObject.username == authentication.name or hasRole('ADMIN')")
public UserDetails loadUserById(Long userId) {
// 只允許查看自己的詳細信息或者管理員查看
return userDetails;
}
}
總結(jié)
Spring Security的授權(quán)機制以AccessDecisionManager和投票決策系統(tǒng)為核心,提供了一套靈活而強大的訪問控制框架。通過責(zé)任鏈模式和策略模式的結(jié)合,它實現(xiàn)了授權(quán)邏輯的模塊化和可擴展性。AccessDecisionManager協(xié)調(diào)多個AccessDecisionVoter,根據(jù)不同的投票策略做出最終授權(quán)決策,支持從寬松到嚴格的各種安全需求。內(nèi)置的投票者如RoleVoter和WebExpressionVoter滿足了基本授權(quán)場景,而自定義投票者則使復(fù)雜的業(yè)務(wù)規(guī)則得以實現(xiàn)。方法級安全控制進一步擴展了授權(quán)能力,使開發(fā)者能夠在業(yè)務(wù)方法層面應(yīng)用精細的權(quán)限管理。理解并掌握這套授權(quán)機制,開發(fā)者可以構(gòu)建既安全又靈活的企業(yè)級應(yīng)用,有效平衡安全需求與用戶體驗。在安全威脅日益復(fù)雜的今天,Spring Security的授權(quán)框架為開發(fā)者提供了應(yīng)對挑戰(zhàn)的有力工具,使復(fù)雜的授權(quán)邏輯變得清晰可維護,為應(yīng)用系統(tǒng)的安全基礎(chǔ)奠定了堅實基礎(chǔ)。
到此這篇關(guān)于SpringSecurity授權(quán)機制的實現(xiàn)(AccessDecisionManager與投票決策)的文章就介紹到這了,更多相關(guān)SpringSecurity授權(quán)機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity實現(xiàn)權(quán)限認證與授權(quán)的使用示例
- SpringSecurity進行認證與授權(quán)的示例代碼
- springSecurity用戶認證和授權(quán)的實現(xiàn)
- SpringBoot整合SpringSecurity認證與授權(quán)
- 深入淺析springsecurity入門登錄授權(quán)
- SpringSecurityOAuth2實現(xiàn)微信授權(quán)登錄
- SpringBoot+SpringSecurity實現(xiàn)基于真實數(shù)據(jù)的授權(quán)認證
- springsecurity第三方授權(quán)認證的項目實踐
- SpringSecurity數(shù)據(jù)庫進行認證和授權(quán)的使用
相關(guān)文章
Spring BeanFactory和FactoryBean區(qū)別解析
這篇文章主要介紹了Spring BeanFactory和FactoryBean區(qū)別解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03
SpringBoot加載多個配置文件實現(xiàn)dev、product多環(huán)境切換的方法
這篇文章主要介紹了SpringBoot加載多個配置文件實現(xiàn)dev、product多環(huán)境切換,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
SpringBoot JPA出現(xiàn)錯誤:No identifier specified&nb
這篇文章主要介紹了SpringBoot JPA出現(xiàn)錯誤:No identifier specified for en解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Java根據(jù)模板實現(xiàn)excel導(dǎo)出標準化
這篇文章主要為大家詳細介紹了Java如何根據(jù)模板實現(xiàn)excel導(dǎo)出標準化,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考下2024-03-03
將字符串數(shù)字格式化為樣式1,000,000,000的方法
這篇文章主要介紹了將字符串數(shù)字格式化為樣式1,000,000,000的方法,有需要的朋友可以參考一下2014-01-01
JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用
今天帶大家學(xué)習(xí)JavaWeb的相關(guān)知識,文章圍繞著JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用展開,文中有非常詳細的介紹,需要的朋友可以參考下2021-06-06

