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

