Spring Security小教程 Vol 7. 訪問規(guī)則ConfigAttribute

前言

從這章開始的三個章節(jié)主要介紹訪問控制最重要的三塊組件:

  1. 訪問規(guī)則ConfigAttribute
  2. 訪問決策AccessDecisionVoter
  3. 訪問控制整體調(diào)度AccessDecisionManager
    最基礎(chǔ)的背景知識的鋪墊已經(jīng)在上一個章節(jié)說明竣稽,如閱讀中又不理解相關(guān)功能模塊職責(zé)的話可以再翻到上一章節(jié)復(fù)習(xí)下哲嘲。

第七期 訪問規(guī)則ConfigAttribute

  1. ConfigAttribute的常用組件
  2. WebExpressionConfigAttribute
  3. SecurityConfig
  4. PostInvocationExpressionAttribute

一、ConfigAttribute的常用組件

ConfigAttribute作為訪問控制模板用于“描述”規(guī)則的組件座硕,最常見的方式主要是兩種一種是基于注解的一種是基于JavaConfig在Web配置中進行配置的意乓。
其中Spring Security對應(yīng)方法級的注解主要又可以分為兩類:
第一類 @Secured 注解 - secured-annotations
第二類 @PreAuthorize, @PreFilter, @PostAuthorize and @PostFilter - pre-post-annotations

ConfigAttribute的常用組件

1.1 WebExpressionConfigAttribute

WebExpressionConfigAttribute是我們最早也是最常見的訪問控制方式。比如下面的代碼形式

 http.authorizeRequests()
        .antMatchers("/").access("hasAnyRole('ANONYMOUS', 'USER')")
        .antMatchers("/login/*").access("hasAnyRole('ANONYMOUS', 'USER')")
        .antMatchers("/logout/*").access("hasAnyRole('ANONYMOUS', 'USER')")
        .antMatchers("/admin/*").access("hasRole('ADMIN')")
        .antMatchers("/events/").access("hasRole('ADMIN')")
        .antMatchers("/**").access("hasRole('USER')")

基于Web表達是可以對目標(biāo)URL的模式進行訪問控制,而控制檢查的規(guī)則最常見兩種方式一種是基于角色(Role-Based),另一種是基于表達式(Expressions-Based)扰藕。
演示代碼中使用的是access的表達式進行控制,同樣的也可以直接使用下面的形式通過角色來達到同樣的效果裙盾。

        .antMatchers("/logout/*").hasAnyRole("USER","ANONYMOUS")

可以說基于Web表達式對于Web資源的控制是我們最長見的方式实胸。
除此以外他嫡,access還有另外一個很酷炫的功能就是通過表達式將判斷的方法委托表達式內(nèi)的方法進行判斷番官。如果最終表達式返回false則會返回403禁止訪問,true則表示授權(quán)訪問钢属。
舉個例子徘熔,假設(shè)我們系統(tǒng)內(nèi)針對/user路徑需要對用戶名進行判斷,如果用戶名不是‘user’則不可以訪問淆党。我們把對應(yīng)判斷的代碼寫在一個CustomWebSecurity的Bean中酷师。

@Component()
public class CustomWebSecurity {
        public boolean check(Authentication authentication) {
            return  (authentication.getName().equals("user"));
        }
}

然后我們通過修改Web表達式我訪問控制向/user的路徑增加這樣一個訪問控制的表達式讶凉。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user").access("@customWebSecurity.check(authentication)")
                .and()
                .formLogin();
    }

最后,我們?yōu)榱诉_到測試的目的對應(yīng)的添加兩個用戶

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(User.withUsername("user").password("{noop}password").roles("USER"));
        auth.inMemoryAuthentication().withUser(User.withUsername("test").password("{noop}password").roles("USER"));
    }

整個測試的Security配置類如下:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user").access("@customWebSecurity.check(authentication)")
                .and()
                .formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(User.withUsername("user").password("{noop}password").roles("USER"));
        auth.inMemoryAuthentication().withUser(User.withUsername("test").password("{noop}password").roles("USER"));
    }
}

然后山孔,我們分別登錄用戶進行測試懂讯,首先我們登錄user的賬戶。


登錄界面

輸入user和password之后台颠,我們可以正確的訪問/user的頁面褐望。


正確的/user頁面

但當(dāng)我們使用test和password以test用戶身份登錄時,因為無法通過access中的check方法對用戶名的檢查串前,所以我們便會得到一個403禁止訪問的錯誤瘫里。
image.png

1.2 SecurityConfig

說完了,最常用的WebExpressionConfigAttribute了解了基于角色與表達式對目標(biāo)地址進行訪問控制的配置之后荡碾,接下來我們分別對兩個基于方法級進行控制的配置展開介紹谨读。
首先我們需要在相關(guān)的JavaConfig中激活相關(guān)的方法級的驗證。

@EnableGlobalMethodSecurity(securedEnabled = true) // <--打開Secured注解配置
public class MethodSecurityConfig {
// ...
}

之后我們便可以在我們需要進行權(quán)限驗證的地方增加相關(guān)的注解和規(guī)則:

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

@Secured("ROLE_TELLER")中的表達式便是驗證規(guī)則坛吁,所有的AccessDecisionVoter都會一次檢查是否對該表達式支持劳殖,比如這個例子里,RoleVoter便會對該規(guī)則進行表決:如果當(dāng)前訪問的用戶擁有TELLER的角色拨脉,那么就可以繼續(xù)執(zhí)行該方法闷尿;如果不沒有對應(yīng)的角色,那么就會返回一個403的錯誤女坑。
同理@Secured("IS_AUTHENTICATED_ANONYMOUSLY")對應(yīng)的便是AuthenticatedVoter填具。
利用這種特性,我們也可以通過自定義相關(guān)表達式與客制化對應(yīng)的AccessDecisionVoter來完成特有的訪問控制邏輯匆骗。

1.3 PostInvocationExpressionAttribute

最后則是利用切面進行訪問控制邏輯的@Pre與@Post注解劳景。同樣的如果需要使用這種方法級的配置,也需要激活對應(yīng)的配置碉就。

@EnableGlobalMethodSecurity(prePostEnabled = true) // <--打開Pre與Post注解配置
public class MethodSecurityConfig {
// ...
}

與@Secured注解不同盟广,如果激活了改配置,則可以使用以下四個注解對Java方法級進行訪問控制和處理:

  • @PreAuthorize
  • @PreFilter
  • @PostAuthorize
  • @PostFilter

@PreAuthorize

其中比較常用的可能是@PreAuthorize注解瓮钥,@PreAuthrize在功能上與@Secured基本是一致的筋量,便是在運行被注解的方法事前先對規(guī)則進行投票,如果通過之后則授權(quán)進行訪問碉熄。相比@Secured而言@PreAuthrize并不是依靠不同的AccessDecisionVoter來完成桨武,而是依靠其編寫的各種表達式的值進行判斷。
比如我們對方法入?yún)⒌挠脩裘M行判斷锈津,則可使用

@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);

或者

@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);

@PreAuthorize的表達式是非常強大工具呀酸,畢竟注入Authentication對象的方法在寫測試用例的時候就非常的痛苦了……

public void doSomething(Contact contact,@Autowired Authentication authentication){
    if (contact.name == authentication.name){   
    ///
   }
};

相對先驗的@PreAuthorize來說后驗的@PostAuthorize注解的使用場景就基本很罕見了。是一個幾乎可以忽略的注解琼梆。

@PreFilter & @PostFilter

接下來說兩個其他教程中都不太提到的Spring Security中的兩個Filter注解性誉。這兩個注解的本質(zhì)是通過在方法級編寫了一個Spring-EL表達式對方法使用和返回的集合進行過濾窿吩。
比如我們最常見的場景便是不同用戶可能生成的菜單可能不同,我們可能會給每一個菜單都賦予一個Permission權(quán)限错览。但是如果在這里展開怕是3000個字也說不清楚纫雁,有機會單獨在ACL部分做一個實戰(zhàn)型的說明。
這邊舉個簡單的例子倾哺,假設(shè)我們現(xiàn)在對用戶使用的一個集合進行過濾先较,如果規(guī)則是只可訪問奇數(shù)ID的對象。換言之悼粮,過濾的規(guī)則則是“當(dāng)ID為偶數(shù)”闲勺。

   @PreFilter(filterTarget="ids", value="filterObject%2==0")
 
   public void doSomething(List<Integer> ids) {
 
   }

如果是對方法返回值的集合進行過濾,則需要使用@PostFilter

   @PostFilter("filterObject.id%2==0")
  public List<User> findAll() {

      List<User> userList = new ArrayList<User>();

      User user;

      for (int i=0; i<10; i++) {

         user = new User();

         user.setId(i);

         userList.add(user);

      }

      return userList;
   }

在SpringSecurity中也內(nèi)建了一部分表達式規(guī)則扣猫,如基于Permission對相應(yīng)對象做權(quán)限驗證過濾:

@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();

而這一部分便設(shè)計了PermissionEvaluator權(quán)限評估器還完成相應(yīng)進行目標(biāo)領(lǐng)域?qū)ο蟛僮魉枰臋?quán)限邏輯菜循。而這一部分則是在ACL客制化的重點。

PermissionEvaluator接口

結(jié)尾

這一期稍微詳細的帶大家走馬觀花般的了解了下Spring Security提供訪問控制各種配置方法和其使用場景申尤。
因為針對這一部分如果過度展開脫離實戰(zhàn)場景也非常難掌握癌幕,所以這一期的真的就只是讓大家了解Spring Security針對不同的訪問控制顆粒細度應(yīng)該怎么配置,比如URL級昧穿、代碼方法級勺远、領(lǐng)域集合級別的權(quán)限過濾或者是客制化對應(yīng)的控制邏輯。
下一期我們將先對訪問控制另外一個核心时鸵,即如何針對配置進行處理的AccessDecisionVoter接口組件進行說明胶逢。
我們下期再見。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饰潜,一起剝皮案震驚了整個濱河市初坠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彭雾,老刑警劉巖碟刺,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異薯酝,居然都是意外死亡半沽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門吴菠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來者填,“玉大人,你說我怎么就攤上這事橄务♂M校” “怎么了穴亏?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵蜂挪,是天一觀的道長重挑。 經(jīng)常有香客問我,道長棠涮,這世上最難降的妖魔是什么谬哀? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮严肪,結(jié)果婚禮上史煎,老公的妹妹穿的比我還像新娘。我一直安慰自己驳糯,他們只是感情好篇梭,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酝枢,像睡著了一般恬偷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帘睦,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天袍患,我揣著相機與錄音,去河邊找鬼竣付。 笑死诡延,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的古胆。 我是一名探鬼主播肆良,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逸绎!你這毒婦竟也來了妖滔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤桶良,失蹤者是張志新(化名)和其女友劉穎座舍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陨帆,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡曲秉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疲牵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片承二。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纲爸,靈堂內(nèi)的尸體忽然破棺而出亥鸠,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布负蚊,位于F島的核電站神妹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏家妆。R本人自食惡果不足惜鸵荠,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伤极。 院中可真熱鬧蛹找,春花似錦、人聲如沸哨坪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽当编。三九已至彼硫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凌箕,已是汗流浹背拧篮。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留牵舱,地道東北人串绩。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像芜壁,于是被迫代替她去往敵國和親礁凡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容