Spring Security小教程 Vol 8. AccessDecisionVoter組件介紹

第八期 AccessDecisionVoter組件介紹

這一期主要我們將介紹訪問控制三劍客負責對授權規(guī)則做角色的組件——AccessDecisionVoter接口揖盘。以及對Spring Security默認提供的幾個基礎AccessDecisionVoter實現(xiàn)類做一個詳細的說明乘瓤,最后我們將會客制化一個基于時間的AccessDecisionVoter實現(xiàn)用于實戰(zhàn)說明憔狞。

  • AccessDecisionVoter接口說明
  • Spring Security的AccessDecisionVoter
  • 客制化實例:基于時間的AccessDecisionVoter

一呐萨、AccessDecisionVoter接口說明

AccessDecisionVoter接口說明

AccessDecisionVoter主要的職責就是對它所對應的訪問規(guī)則作出判斷帜羊,當前的訪問規(guī)則是否可以得到授權市袖。
AccessDecisionVoter接口的主要方法其實與之前的AuthenticationProvider非常的相似谴分。

    boolean supports(ConfigAttribute attribute);

    int vote(Authentication authentication, S object,
            Collection<ConfigAttribute> attributes);
  • supports方法用于判斷對于當前ConfigAttribute訪問規(guī)則是否支持锈麸;
  • 如果支持的情況下,vote方法對其進行判斷投票返回對應的授權結果牺蹄。
    最終的授權結果一共有三種,分別是同意忘伞、棄權和反對。說實話這個規(guī)則和聯(lián)合國安理會投票差不多性質。當前一個訪問可能存在多個規(guī)則的情況下氓奈,每一個AccessDecisionVoter投出自己的那一票匿刮,最終的投票結果是還是要看當前的投票規(guī)則,比如是超過1/3還是要過半數探颈。而投票規(guī)則的判斷則是被放置了在了AccessDecisionManager進行完成熟丸。
    int ACCESS_GRANTED = 1;
    int ACCESS_ABSTAIN = 0;
    int ACCESS_DENIED = -1;

二、 Spring Security的AccessDecisionVoter

通過上面對于AccessDecisionVoter的基本介紹伪节,我們得知了一個設計上的大原則:AccessDecisionVoter的實現(xiàn)是為了滿足對應規(guī)則ConfigAttribute光羞。大體上來說AccessDecisionVoter是與ConfigAttribute一一對應的。
讓我們回一下在上一期我們介紹的主要的幾種ConfigAttribute實現(xiàn):

  • 基于Web表達式的WebExpressionConfigAttribute
  • 基于@Secured注解的SecurityConfig
  • 基于@Pre-@Post注解的PostInvocationExpressionAttribute
    我們可以在下圖中輕松的找到他門對應的AccessDecisionVoter怀大。
    主要的AccessDecisionVoter

    這邊我們重點說一下在客制化場景下被利用的SecurityConfig配置和他默認的兩個AccessDecisionVoter:
  • RoleVoter
  • AuthenticatedVoter
    首先纱兑,我們來回憶下SecurityConfig的使用形式,即利用@Secured注解編寫一個表達式:
@Secured("ROLE_USER")
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")

我們了解到了AccessDecisionVoterConfigAttribute的關聯(lián)關系是通過supports方法進行判斷化借,我們分別對RoleVoterAuthenticatedVoter的supports方法進行瀏覽:

RoleVoter
RoleVoter是Spring Security中默認基于角色規(guī)則的核心組件潜慎。在UserDetailsService中創(chuàng)建用戶我們都會需要設置對用用戶的角色信息。在默認配置下用戶的角色信息都是以"ROLE_"+角色名的形式存儲的蓖康。
對應的在RoleVoter的supports方法中會對表達式是否以'ROLE_'開始作為對應啟用規(guī)則的判斷铐炫。如果規(guī)則表達式是以ROLE_開始的,RoleVoter則會去遍歷對用Authentication是否存在對應的角色蒜焊,如果存在則返回通過倒信,如果不存在則返回拒絕。

public class RoleVoter implements AccessDecisionVoter<Object> {
    // ~ Instance fields
    // ================================================================================================

    private String rolePrefix = "ROLE_";

    // ~ Methods
    // ========================================================================================================

    public String getRolePrefix() {
        return rolePrefix;
    }

    /**
     * Allows the default role prefix of <code>ROLE_</code> to be overridden. May be set
     * to an empty value, although this is usually not desirable.
     *
     * @param rolePrefix the new prefix
     */
    public void setRolePrefix(String rolePrefix) {
        this.rolePrefix = rolePrefix;
    }

    public boolean supports(ConfigAttribute attribute) {
        if ((attribute.getAttribute() != null)
                && attribute.getAttribute().startsWith(getRolePrefix())) {
            return true;
        }
        else {
            return false;
        }
    }
}

AuthenticatedVoter
AuthenticatedVoter的使用場景就比較特殊泳梆,他并不是一個基于身份信息的訪問控制鳖悠,而是對于對應Auhentication的認證形式的一個判斷。在之前的身份驗證部分我們有了解過优妙,在Spring Security設計中乘综,我們可以銅鼓RememberMeService的方式不使用用戶名和密碼,而是通過存儲于Cookie的信息進行授權登錄套硼。在日常工程中卡辰,對于一些敏感操作,我們要求當前的用戶并不是一個基于歷史進行授權認證的用戶熟菲,比如在進行支付的情況下看政,如果我們希望用戶是在本次訪問中是通過用戶名和密碼進行登錄展開的會話操作,而不是一個基于一個月前cookies進行登錄都有用戶抄罕。在這個場景下我們需要便可以使用@Secured("IS_AUTHENTICATED_FULLY")去限定用戶是一個通過完全驗證的用戶,而不是通過RememberMe方式認證的用戶于颖。
AuthenticatedVoter的supports方法中呆贿,便會判斷當前的表達式是為他所支持的三種認證方法的訪問控制:

  • IS_AUTHENTICATED_FULLY
  • IS_AUTHENTICATED_REMEMBERED
  • IS_AUTHENTICATED_ANONYMOUSLY
    如果完全匹配,則會當前的Authentication對象的授權模式進行判斷,返回相應的投票結果做入。
public class AuthenticatedVoter implements AccessDecisionVoter<Object> {
    // ~ Static fields/initializers
    // =====================================================================================

    public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY";
    public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED";
    public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY";
    // ~ Instance fields
    // ================================================================================================

    private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();

    // ~ Methods
    // ========================================================================================================

    private boolean isFullyAuthenticated(Authentication authentication) {
        return (!authenticationTrustResolver.isAnonymous(authentication) && !authenticationTrustResolver
                .isRememberMe(authentication));
    }

    public boolean supports(ConfigAttribute attribute) {
        if ((attribute.getAttribute() != null)
                && (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())
                        || IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute()) || IS_AUTHENTICATED_ANONYMOUSLY
                            .equals(attribute.getAttribute()))) {
            return true;
        }
        else {
            return false;
        }
    }
}

三冒晰、 客制化實例:基于時間的AccessDecisionVoter

對于AccessDecisionVoter結構、責任和Spring Security中提供的實現(xiàn)類有了一個基礎的了解后竟块。我們通過一個客制化的實例來加強這部分的理解壶运。
我們將客制化一個基于時間的訪問控制,在系統(tǒng)時間的分鐘數是奇數的情況下才可以被訪問浪秘,比如10點01分可以訪問蒋情,但是10點02分則不可以被訪問。

設計規(guī)則

首先耸携,我們對訪問規(guī)則進行設計棵癣。我們如同RoleVoterAuthenticatedVoter一樣基于@Secured注解的表達式進行擴展。我們擬定的規(guī)則名為"MINUTE_ODD"夺衍,當方法級被注解了@Secured("MINUTE_ODD")情況下狈谊,表示當前方法只有在滿足系統(tǒng)時間的分鐘數為奇數下才可以被訪問。

客制化MinuteBasedVoter

接下來沟沙,我們編寫一個MinuteBasedVoter擴展AuthenticatedVoter河劝。

public class MinuteBasedVoter implements AccessDecisionVoter {
}

然后,我們實現(xiàn)對應的suppors方法用于完成我們對我們擬定的規(guī)則的判斷矛紫。當入參ConfigAttribute 的表達式屬性與我們預設的"MINUTE_ODD"一致時丧裁,那么我們便返回true告知框架,MinuteBasedVoter需要對該規(guī)則進行vote的投票操作含衔。

public class MinuteBasedVoter implements AccessDecisionVoter {
    public static final String IS_MINUTE_ODD= "MINUTE_ODD";

    @Override
    public boolean supports(ConfigAttribute attribute) {
        if ((attribute.getAttribute() != null)
                && attribute.getAttribute().equals(IS_MINUTE_ODD)) {
            return true;
        }
        else {
            return false;
        }
    }


    @Override
    public boolean supports(Class clazz) {
        return true;
    }
}

最后煎娇,我們將vote的投票核心業(yè)務邏輯完成:當時間為奇數的時候則投贊同票,而在時間為偶數的時候則投一張明確的反對票贪染。

    @Override
    public int vote(Authentication authentication, Object object, Collection collection) {
        if(LocalDateTime.now().getMinute() % 2 != 0){
            return ACCESS_GRANTED;

        }else{
            return ACCESS_DENIED;
        }
    }

Java Config配置

最后缓呛,說一下如何將新的AccessDecisionVoter添加到現(xiàn)有的AccessDecisionManager中。我自己也百度了一下了中文世界和英文世界關于這方便的示例已經官方文檔杭隙,真的是五花八門都有哟绊。最常見的是重新組織了一個AccessDecisionManager注入回Spring Security中,我很不推薦自己在方法中去new一個AccessDecisionManager痰憎。因為AccessDecisionManager的初始化過程中涉及的不只是AccessDecisionVoter票髓,一不小心可能因為少設置什么組件就導致一部分默認行為沒被正確的配置上去。
我推薦初學者方法是對于擴展Secured這類基于方法級的注解铣耘,單獨新建一個Java Config類洽沟,然后重寫原有框架中初始化AccessDecisionManager的方法:

@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
@Configuration
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
    @Override
    protected AccessDecisionManager accessDecisionManager() {
        AffirmativeBased affirmativeBased = (AffirmativeBased) super.accessDecisionManager();
        affirmativeBased.getDecisionVoters().add(new MinuteBasedVoter());
        return affirmativeBased;
    }
}

雖然代碼可能丑、有對類型強轉蜗细,相對來說好理解控制很多裆操。
在添加了MethodSecurityConfiguration的Java Config之后怒详,我們在對受到@Secured("MINUTE_ODD")注解限制的controller方式時便會看到以下的投票日志:

Secure object: ReflectiveMethodInvocation: public java.lang.String Attributes: [MINUTE_ODD]
Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@456f4439, returned: 0
Voter: org.springframework.security.access.vote.RoleVoter@38b13fa8, returned: 0
Voter: org.springframework.security.access.vote.AuthenticatedVoter@590fa701, returned: 0
Voter: com.newnil.demo.security.MinuteBasedVoter@135c04e9, returned: 1
Authorization successful

AccessDecisionVoter組件們依次投票,而因為當前時間是奇數踪区,所以我們的MinuteBasedVoter投出一票值為1的贊同票昆烁。

結尾

這一期詳細介紹了AccessDecisionVoter這一為訪問控制提供核心判斷及投票的組件。同時也通過框架默認提供與客制化實現(xiàn)了解了其工作原理缎岗。
下一期我們將最后一個核心組件AccessDecisionManager是如何對所有AccessDecisionVoter的投票結果進行匯總静尼,以及如何以什么評價規(guī)則告知框架最終的授權結果進行說明。
我們下期再見传泊。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末鼠渺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子或渤,更是在濱河造成了極大的恐慌系冗,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薪鹦,死亡現(xiàn)場離奇詭異掌敬,居然都是意外死亡,警方通過查閱死者的電腦和手機池磁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門奔害,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人地熄,你說我怎么就攤上這事华临。” “怎么了端考?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵雅潭,是天一觀的道長。 經常有香客問我却特,道長扶供,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任裂明,我火速辦了婚禮椿浓,結果婚禮上,老公的妹妹穿的比我還像新娘闽晦。我一直安慰自己扳碍,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布仙蛉。 她就那樣靜靜地躺著笋敞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捅儒。 梳的紋絲不亂的頭發(fā)上液样,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天振亮,我揣著相機與錄音巧还,去河邊找鬼鞭莽。 笑死,一個胖子當著我的面吹牛麸祷,可吹牛的內容都是我干的澎怒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼阶牍,長吁一口氣:“原來是場噩夢啊……” “哼喷面!你這毒婦竟也來了?” 一聲冷哼從身側響起走孽,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤惧辈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后磕瓷,有當地人在樹林里發(fā)現(xiàn)了一具尸體盒齿,經...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年困食,在試婚紗的時候發(fā)現(xiàn)自己被綠了边翁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡硕盹,死狀恐怖符匾,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情瘩例,我是刑警寧澤啊胶,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站垛贤,受9級特大地震影響焰坪,放射性物質發(fā)生泄漏。R本人自食惡果不足惜南吮,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一琳彩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧部凑,春花似錦露乏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至比勉,卻和暖如春劳较,著一層夾襖步出監(jiān)牢的瞬間驹止,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工观蜗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留臊恋,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓墓捻,卻偏偏與公主長得像抖仅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子砖第,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容

  • 我不是一個孤陋寡聞的人撤卢。 幾年前,就聽說了《夜聽》這檔欄目非澄嗉妫火放吩。劉筱這個人也是很多人知道的一號人物。 ...
    紅小楊閱讀 445評論 0 1
  • 2018-5-7 大雪 沒錯羽杰,今天的烏魯木齊下了大雪渡紫。 你在擔心一件事情,就是自己還可不可靠忽洛。 你說呢腻惠?無論如...
    蟋蟀王閱讀 119評論 0 0
  • 1970年陽春三月,沈家大閨女出世欲虚,這就是我的大姐集灌。 那年沈家還居住在云巖鄉(xiāng)山前村,老屋前面有條清...
    我是貓小懶閱讀 858評論 0 2