溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何使用@Secured注解限制方法調(diào)用

發(fā)布時(shí)間:2022-10-18 16:01:36 來(lái)源:億速云 閱讀:354 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容介紹了“如何使用@Secured注解限制方法調(diào)用”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

保護(hù)方法應(yīng)用

在Spring Securoty實(shí)現(xiàn)方法級(jí)別的安全性最常見(jiàn)的方法是使用特定的注解.將這些注解應(yīng)用到需要保護(hù)的方法上.

Spring Security 提供了三種不同方式的安全注解.

  • Spring 自帶的 @Security 注解.

  • JSR-250 的 @RolesAllow 注解.

  • 表達(dá)式驅(qū)動(dòng)的注解: @PreAythorize , @PostAuthorize ,@PreFilter , @PostFilter .

@Secured和@RolesAllowed方案非常類似,能夠基于用戶所授予的權(quán)限限制對(duì)方法的訪問(wèn)。
當(dāng)我們需要在方法上定義更靈活的安全規(guī)則時(shí),Spring Security提供了@PreAuthorize和@PostAuthorize,
而@PreFilter/@PostFilter能夠過(guò)濾方法返回的以及傳入方法的集合。

下面就開(kāi)始介紹以上注解具體的使用方法.

使用@Secured注解限制方法調(diào)用

為了降低初學(xué)者的學(xué)識(shí)成本,我們使用最簡(jiǎn)單的使用內(nèi)存的用戶存儲(chǔ)來(lái)演示.

/**
 * @author itguang
 * @create
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    //基于內(nèi)存的用戶存儲(chǔ)
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("itguang").password("123456").roles("USER").and()
                .withUser("admin").password("123456").roles("ADMIN");
    }

}

在Spring中如果要啟用基于注解的方法安全性,需要在配置類上使用@EnableGlobalMethodSecurity,如下所示:

/**
 * @author itguang
 * @create
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration

除了@EnableGlobalMethodSecuroty注解外,我們注意到配置類還擴(kuò)展了 GlobalMethodSecurotyConfiguration .
在前面的文章中我們介紹過(guò) web安全的配置類擴(kuò)展了 WebSecurityConfiguration ,與此類似,這個(gè)類能勾為方法級(jí)別的安全性提供精細(xì)的配置.

我們還可以看到 @EnableGlobalMethodSecuroty注解 的參數(shù) securedEnabled 設(shè)置為了了true,這樣的話Spring Security就會(huì)包裝帶有
@Secured 注解的方法,例如:

@RequestMapping("/admin")
    @Secured("ROLE_ADMIN")
    public String admin(){
        return "admin";
    }

由于方法上添加了 @Secured(“ROLE_ADMIN”) 注解,當(dāng)我們?yōu)g覽器訪問(wèn) /admin 這個(gè)路徑時(shí),Spring就會(huì)執(zhí)行 admin()方法,Spring Security的切面
就會(huì)判斷當(dāng)前用戶是否有 ROLE_ADMIN 的權(quán)限.

看到這里你可能會(huì)有疑惑,為什么是 ROLE_ADMIN 呢,我們的SecurityConfigure 配置類中命名設(shè)置的是 ADMIN 啊.
.withUser("admin").password("123456").roles("ADMIN"); ,其實(shí)這是Spring Security 自動(dòng)為我們添加了 ROLE_ 前綴,
如果我們要寫成 .withUser("admin").password("123456").roles("ADMIN"); 的話,Spring Security就會(huì)報(bào)錯(cuò),

java.lang.IllegalArgumentException: ROLE_ADMIN cannot start with ROLE_ (it is automatically added)

可以看到Spring Security 提醒我們這是自動(dòng)添加的前綴,因此我們么有必要再添加 ROLE_ 前綴.

但是,如果我們要為基于方法的攔截添加 @Secured 注解時(shí),就必須添加 ROLE_ 的前綴.比如: @Secured("ROLE_ADMIN") .

測(cè)試

這里只給出主要的代碼,詳細(xì)代碼請(qǐng)參考源碼,本文最后會(huì)給出.

按照上面的配置完畢,我們啟動(dòng)項(xiàng)目,訪問(wèn) http://localhost/admin ,會(huì)提示讓我們登陸,我們首先輸入一個(gè)沒(méi)有 ROLE_ADMIN 權(quán)限的用戶,
用戶名:itguang,密碼:123456. 點(diǎn)擊登陸,會(huì)發(fā)現(xiàn)瀏覽器返回下面的頁(yè)面:

http狀態(tài)碼為 403,很明顯就是無(wú)權(quán)限訪問(wèn).接下來(lái)我們?cè)谟?admin 用戶登陸,機(jī)會(huì)發(fā)現(xiàn)瀏覽器正確返回了 admin 字符串.

另外,@Secured會(huì)使用一個(gè)String數(shù)組作為參數(shù),每個(gè)String值就是一個(gè)權(quán)限,調(diào)用這個(gè)方法的用戶至少要具備其中一個(gè)權(quán)限.如下面示例:

@RequestMapping("/hello")
    @Secured({"ROLE_ADMIN","ROLE_USER"})
    public String hello() {

        return "hello Spring Security";

    }

itguang 和 admin 這兩個(gè)用戶都可以訪問(wèn).如果方法被沒(méi)有被認(rèn)證的用戶或者沒(méi)有相應(yīng)權(quán)限的用戶訪問(wèn),就會(huì)拋出一個(gè)Spring Security 異常.
(可能是AuthenticationException或AccessDeniedException的子類),他們是非檢查時(shí)異常,這個(gè)一場(chǎng)病最終必須被捕獲或者處理.
如果被保護(hù)的方法是在web中被調(diào)用的,這個(gè)異常會(huì)被Spring Security 的過(guò)濾器自動(dòng)處理.否則的話,你需要編寫代碼來(lái)處理這個(gè)異常.

在Spring Security中使用JSR-250的@RolesAllowed注解

@RolesAllowed注解和@Secured注解在各個(gè)方面基本上都是一致的。唯一顯著的區(qū)別在于@RolesAllowed是JSR-250定義的Java標(biāo)準(zhǔn)注解.

如果選擇使用@RolesAllowed的話,需要將@EnableGlobalMethodSecurity的jsr250Enabled屬性設(shè)置為true,以開(kāi)啟此功能.

/**
 * @author itguang
 * @create
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration
    //基于內(nèi)存的用戶存儲(chǔ)
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("itguang").password("123456").roles("USER").and()
                .withUser("admin").password("123456").roles("ADMIN");
    }
}

盡管我們這里只是啟用了jsr250Enabled,但需要說(shuō)明的一點(diǎn)是這與securedEnabled并不沖突。這兩種注解風(fēng)格可以同時(shí)啟用.

如下:

@RequestMapping("/test1")
    @RolesAllowed("ROLE_ADMIN")
    public String test1(){
        return "test1";
    }

這兩個(gè)注解都有一個(gè)不足之處,它們只能根據(jù)用戶有沒(méi)有授予特定的權(quán)限來(lái)限制方法的調(diào)用,在判斷方式是否執(zhí)行方面,無(wú)法使用其他的因素.
接下來(lái),我們看一下如何組合使用SpEL與Spring Security所提供的方法調(diào)用前后注解,實(shí)現(xiàn)基于表達(dá)式的方法安全性.

使用表達(dá)式實(shí)現(xiàn)方法級(jí)別的安全性

Spring Security 3.0引入了幾個(gè)新注解,它們使用SpEL能夠在方法調(diào)用上實(shí)現(xiàn)更有意思的安全性約束。

  • @PreAuthorize :在方法調(diào)用之前,基于表達(dá)式的計(jì)算結(jié)果來(lái)限制對(duì)方法的訪問(wèn)

  • @PostAuthorize 允許方法調(diào)用,但是如果表達(dá)式計(jì)算結(jié)果為false,將拋出一個(gè)安全性異常

  • @PostFilter 允許方法調(diào)用,但必須按照表達(dá)式來(lái)過(guò)濾方法的結(jié)果

  • @PreFilter 允許方法調(diào)用,但必須在進(jìn)入方法之前過(guò)濾輸入值

這些注解的參數(shù)都可接受一個(gè)SPEL 表達(dá)式.表達(dá)式可以是任意合法的SPEL表達(dá)式.

如果表達(dá)式的計(jì)算結(jié)果為true,那么安全規(guī)則通過(guò),否則就會(huì)失敗。安全規(guī)則通過(guò)或失敗的結(jié)果會(huì)因?yàn)樗褂米⒔獾牟町惗兴煌?/p>

我們需要將@EnableGlobalMethod-Security注解的 prePostEnabled 屬性設(shè)置為true,從而啟用它們:

/**
 * @author itguang
 * @create
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true,prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration
    //基于內(nèi)存的用戶存儲(chǔ)
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("itguang").password("123456").roles("USER").and()
                .withUser("admin").password("123456").roles("ADMIN");
    }


}

現(xiàn)在方法調(diào)用前后的注解都已經(jīng)啟用了.

@PreAuthorize和@PostAuthorize

@PreAuthorize和@PostAuthorize,它們能夠基于表達(dá)式的計(jì)算結(jié)果來(lái)限制方法的訪問(wèn)。
在定義安全限制方面,表達(dá)式帶了極大的靈活性。通過(guò)使用表達(dá)式,只要我們能夠想象得到,就可以定義任意允許訪問(wèn)或不允許訪問(wèn)方法的條件。

@PreAuthorize和@PostAuthorize之間的關(guān)鍵區(qū)別在于表達(dá)式執(zhí)行的時(shí)機(jī)。
@PreAuthorize的表達(dá)式會(huì)在方法調(diào)用之前執(zhí)行,如果表達(dá)式的計(jì)算結(jié)果不為true的話,將會(huì)阻止方法執(zhí)行。
與之相反,@PostAuthorize的表達(dá)式直到方法返回才會(huì)執(zhí)行,然后決定是否拋出安全性的異常。

在方法調(diào)用之前驗(yàn)證權(quán)限,看下面的例子:

/**
 * @author itguang
 * @create
@RestController
public class UserController

    @RequestMapping("/addUser")
    @PreAuthorize("hasRole('ROLE_USER') and #userEntity.password>8 or hasRole('ROLE_ADMIN')")
    public String addUser(UserEntity userEntity){
        return "addUser ok";
    }

}

這段代碼是什么意思呢? 首先我們讓訪問(wèn) /addUser 的用戶 必須是擁有 ROLE_USER 的用戶,并且密碼長(zhǎng)度大于8,或者擁有 ROLE_ADMIN 權(quán)限的.
這如果如使用@Secured或者RoleAllowd 是實(shí)現(xiàn)不了的.而使用 @PreAuthorized 恰好能適用這些場(chǎng)景.
表達(dá)式中 #userEntity 直接使用了方法中的同名參數(shù),這使得Spring Security 能夠檢查傳入方法的參數(shù).并將這些參數(shù)用于認(rèn)證決策的指定.

在方法調(diào)用之后驗(yàn)證權(quán)限,看下面的例子:

@RequestMapping("/getUser/{username}")
    @PostAuthorize("returnObject.username == principal.username")
    public UserEntity getUser(@PathVariable(value = "username") String username) {

        //模擬從數(shù)據(jù)庫(kù)中查找
        UserEntity userEntity = new UserEntity(username);

        return

啟動(dòng)項(xiàng)目,我們首先訪問(wèn):http://localhost/getUser/itguang,會(huì)讓我們登陸,我們用itguang 用戶登陸,發(fā)現(xiàn)能夠正確返回,
然后我們?cè)诖嘶A(chǔ)上再訪問(wèn) http://localhost/getUser/admin ,就會(huì)返回http狀態(tài)碼403,禁止訪問(wèn).說(shuō)明配置生效.

下面我們來(lái)解釋一下上面的代碼,為了方便的訪問(wèn)受保護(hù)方法的返回對(duì)象,Spring Security 在SPEL中提供了名為 returnObject的返回變量.
在這里我們知道返回對(duì)象是一個(gè)UserEntity,所以可以直接 returnObject.username 取得里面的參數(shù).
principal 是另一個(gè)Spring Security 內(nèi)置的特殊變量,它代表了當(dāng)前認(rèn)證用戶的主要信息,通常是用戶名和權(quán)限列表.

@PreAuthorize和@PostAuthorize—過(guò)濾方法的輸入和輸出

如果我們希望使用表達(dá)式來(lái)保護(hù)方法的話,那使用@PreAuthorize和@PostAuthorize是非常好的方案。但是,有時(shí)候限制方法調(diào)用太嚴(yán)格了。
有時(shí),需要保護(hù)的并不是對(duì)方法的調(diào)用,需要保護(hù)的是傳入方法的數(shù)據(jù)和方法返回的數(shù)據(jù).

事后對(duì)方法的返回值進(jìn)行過(guò)濾,如下:

@RequestMapping("getAll")
    @PreAuthorize("hasRole('ROLE_USER')")
    @PostFilter("filterObject.enabled == true")
    public List<UserEntity> getAllUser(){

        ArrayList<UserEntity> list = new ArrayList<>();
        list.add(new UserEntity("test1","123456",true));
        list.add(new UserEntity("test1","123456",false));

        return

我們使用了 @PreAuthorize("hasRole('ROLE_USER')") @PostFilter("filterObject.enabled == true") 這兩個(gè)注解,
表明我們希望,用戶必須擁有 ROLE_USER 權(quán)限,并且返回用戶屬性 enabled為true的所有用戶.

表達(dá)式中的 filterObject 引用的是方法返回值List中的某一個(gè)元素,在這里是 UserEntity,并且過(guò)濾出 enabled為true的UserEntity,所以,
我們?yōu)g覽器訪問(wèn) http://localhost/getAll,并用itguang用戶登錄后,返回的只有一條用戶的信息.

如果你覺(jué)得自己的安全表達(dá)式難以控制了,那么就應(yīng)該看一下如何編
寫自定義的許可計(jì)算器(permission eval(232, 232, 232); background: rgb(249, 249, 249);">

@RequestMapping("/delete")
    @PreAuthorize("ROLE_USER")
    @PreFilter("hasPermission(targetObject,'delete')")
    public String getAllUser(List<UserEntity> list){
            //從數(shù)據(jù)庫(kù)中刪除數(shù)據(jù)
            //...

        return "ok";
    }

上面代碼實(shí)際上是在問(wèn)一個(gè)問(wèn)題,”當(dāng)前用戶有權(quán)限刪除目標(biāo)對(duì)象嗎?” 如果有的話,表達(dá)式計(jì)算為true,該對(duì)象會(huì)被刪除,

但是,hasPermission()是哪來(lái)的呢?它的意思是什么?更為重要
的是,它如何知道用戶有沒(méi)有權(quán)限刪除targetObject所對(duì)應(yīng)的
User?

hasPermission()函數(shù)是Spring Security為SpEL提供的擴(kuò)展,它為開(kāi)發(fā)者提供了一個(gè)時(shí)機(jī),能夠在執(zhí)行計(jì)算的時(shí)候插入任意的邏輯。
我們所需要做的就是編寫并注冊(cè)一個(gè)自定義的許可計(jì)算器.

“如何使用@Secured注解限制方法調(diào)用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI