最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Spring Security中權(quán)限注解的使用

 更新時間:2022年05月23日 14:53:37   作者:_江南一點(diǎn)雨  
這篇文章主要為大家詳細(xì)介紹一下Spring Security中權(quán)限注解的使用方法,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)或工作有一定參考價值,需要的可以參考一下

最近有個小伙伴在微信群里問 Spring Security 權(quán)限注解的問題:

很多時候事情就是這么巧,松哥最近在做的 tienchin 也是基于注解來處理權(quán)限問題的,所以既然大家有這個問題,咱們就一塊來聊聊這個話題。

當(dāng)然一些基礎(chǔ)的知識我就不講了,對于 Spring Security 基本用法尚不熟悉的小伙伴,可在公眾號后臺回復(fù) ss,有原創(chuàng)的系列教程。

1. 具體用法

先來看看 Spring Security 權(quán)限注解的具體用法,如下:

@PreAuthorize("@ss.hasPermi('tienchin:channel:query')")
@GetMapping("/list")
public TableDataInfo getChannelList() {
    startPage();
    List<Channel> list = channelService.list();
    return getDataTable(list);
}

類似于上面這樣,意思就是說,當(dāng)前用戶需要具備 tienchin:channel:query 權(quán)限,才能執(zhí)行當(dāng)前的接口方法。

那么要搞明白 @PreAuthorize 注解的原理,我覺得得從兩個方面入手:

  • 首先明白 Spring 中提供的 SpEL。
  • 其次搞明白 Spring Security 中對方法注解的處理規(guī)則。

我們一個一個來看。

2. SpEL

Spring Expression Language(簡稱 SpEL)是一個支持查詢和操作運(yùn)行時對象導(dǎo)航圖功能的強(qiáng)大的表達(dá)式語言。它的語法類似于傳統(tǒng) EL,但提供額外的功能,最出色的就是函數(shù)調(diào)用和簡單字符串的模板函數(shù)。

SpEL 給 Spring 社區(qū)提供一種簡單而高效的表達(dá)式語言,一種可貫穿整個 Spring 產(chǎn)品組的語言。這種語言的特性基于 Spring 產(chǎn)品的需求而設(shè)計,這是它出現(xiàn)的一大特色。

在我們離不開 Spring 框架的同時,其實(shí)我們也已經(jīng)離不開 SpEL 了,因?yàn)樗糜?、太?qiáng)大了,SpEL 在整個 Spring 家族中也處于一個非常重要的位置。但是很多時候,我們對它的只了解一個大概,其實(shí)如果你系統(tǒng)的學(xué)習(xí)過 SpEL,那么上面 Spring Security 那個注解其實(shí)很好理解。

我先通過一個簡單的例子來和大家捋一捋 SpEL。

為了省事,我就創(chuàng)建一個 Spring Boot 工程來和大家演示,創(chuàng)建的時候不用加任何額外的依賴,就最最基礎(chǔ)的依賴即可。

代碼如下:

String expressionStr = "1 + 2";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expressionStr);

expressionStr 是我們自定義的一個表達(dá)式字符串,這個字符串通過一個 ExpressionParser 對象將之解析為一個 Expression,接下來就可以執(zhí)行這個 exp 了。

執(zhí)行的時候有兩種方式,對于我們上面這種不帶任何額外變量的,我們可以直接執(zhí)行,直接執(zhí)行的方式如下:

Object value = exp.getValue();
System.out.println(value.toString());

這個打印結(jié)果為 3。

我記得之前有個小伙伴在群里問想執(zhí)行一個字符串表達(dá)式,但是不知道怎么辦,js 中有 eval 函數(shù)很方便,我們 Java 中也有 SpEL,一樣也很方便。

不過很多時候,我們要執(zhí)行的表達(dá)式可能比較復(fù)雜,這時候上面這種調(diào)用方式就不太夠用了。

此時我們可以為要調(diào)用的表達(dá)式設(shè)置一個上下文環(huán)境,這個時候就會用到 EvaluationContext 或者它的子類,如下:

StandardEvaluationContext context = new StandardEvaluationContext();
System.out.println(exp.getValue(context));

當(dāng)然上面這個表達(dá)式不需要設(shè)置上下文環(huán)境,我舉一個需要設(shè)置上下文環(huán)境的例子。

例如我現(xiàn)在有一個 User 類,如下:

public class User {
    private Integer id;
    private String username;
    private String address;
    //省略 getter/setter
}

現(xiàn)在我的表達(dá)式是這樣:

String expression = "#user.username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("廣州");
user.setUsername("javaboy");
user.setId(99);
ctx.setVariable("user", user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

這個表達(dá)式就表示獲取 user 對象的 username 屬性。將來創(chuàng)建一個 user 對象,放到 StandardEvaluationContext 中,并基于此對象執(zhí)行表達(dá)式,就可以打印出來想要的結(jié)果。

如果我們將 user 對象設(shè)置為 rootObject,那么表達(dá)式中就不需要 user 了,如下:

String expression = "username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("廣州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

表達(dá)式就一個 username 字符串,將來執(zhí)行的時候,會自動從 user 中找到 username 的值并返回。

當(dāng)然表達(dá)式也可以是方法,例如我在 User 類中添加如下兩個方法:

public String sayHello(Integer age) {
    return "hello " + username + ";age=" + age;
}
public String sayHello() {
    return "hello " + username;
}

我們就可以通過表達(dá)式調(diào)用這兩個方法,如下:

調(diào)用有參的 sayHello:

String expression = "sayHello(99)";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("廣州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

就直接寫方法名然后執(zhí)行就行了。

調(diào)用無參的 sayHello:

String expression = "sayHello";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("廣州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

這些就都好懂了。

甚至,我們的表達(dá)式也可以涉及到 Spring 中的一個 Bean,例如我們向 Spring 中注冊如下 Bean:

@Service("us")
public class UserService {
    public String sayHello(String name) {
        return "hello " + name;
    }
}

然后通過 SpEL 表達(dá)式來調(diào)用這個名為 us 的 bean 中的 sayHello 方法,如下:

@Autowired
BeanFactory beanFactory;
@Test
void contextLoads() {
    String expression = "@us.sayHello('javaboy')";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);
    StandardEvaluationContext ctx = new StandardEvaluationContext();
    ctx.setBeanResolver(new BeanFactoryResolver(beanFactory));
    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}

給配置的上下文環(huán)境設(shè)置一個 bean 解析器,這個 bean 解析器會自動跟進(jìn)名字從 Spring 容器中找打響應(yīng)的 bean 并執(zhí)行對應(yīng)的方法。

當(dāng)然,關(guān)于 SpEL 的玩法還有很多,我就不一一列舉了。這里主要是想讓小伙伴們知道,有這么個技術(shù),方便大家理解 @PreAuthorize 注解的原理。

3. @PreAuthorize

接下來我們就回到 Spring Security 中來看 @PreAuthorize 注解。

權(quán)限的實(shí)現(xiàn)方式千千萬,又有各種不同的權(quán)限模型,然而歸結(jié)到代碼上,無非兩種:

基于 URL 地址的權(quán)限處理

基于方法注解的權(quán)限處理

松哥之前的 vhr 使用的是前者。

@PreAuthorize 注解當(dāng)然對應(yīng)的是后者。這次做的 tienchin 項(xiàng)目就是后者,我們來看一個例子:

@PreAuthorize("@ss.hasPermi('tienchin:channel:query')")
@GetMapping("/list")
public TableDataInfo getChannelList() {
    startPage();
    List<Channel> list = channelService.list();
    return getDataTable(list);
}

注解好說,里邊的 @ss.hasPermi('tienchin:channel:query') 是啥意思呢?

ss 是一個注冊在 Spring 容器中的 bean,對應(yīng)的類位于 org.javaboy.tienchin.framework.web.service.PermissionService 中。

很明顯,hasPermi 就是這個類中的方法。

這個 hasPermi 方法的邏輯其實(shí)很簡單:

public boolean hasPermi(String permission) {
    if (StringUtils.isEmpty(permission)) {
        return false;
    }
    LoginUser loginUser = SecurityUtils.getLoginUser();
    if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
        return false;
    }
    return hasPermissions(loginUser.getPermissions(), permission);
}
private boolean hasPermissions(Set<String> permissions, String permission) {
    return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}

這個判斷邏輯很簡單,就是獲取到當(dāng)前登錄的用戶,判斷當(dāng)前登錄用戶的權(quán)限集合中是否具備當(dāng)前請求所需要的權(quán)限。具體的判斷邏輯沒啥好說的,就是看集合中是否存在某個字符串。

那么這個方法是在哪里調(diào)用的呢?

大家知道,Spring Security 中處理權(quán)限的過濾器是 FilterSecurityInterceptor,所有的權(quán)限處理最終都會來到這個過濾器中。在這個過濾器中,將會用到各種投票器、表決器之類的工具,這里我就不細(xì)說了,之前的 Spring Security 系列教程都有詳細(xì)介紹。

在投票器中,我們可以看到專門處理 @PreAuthorize 注解的類 PreInvocationAuthorizationAdviceVoter,我們來看下他里邊的核心方法:

@Override
public int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) {
    PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
    if (preAttr == null) {
        return ACCESS_ABSTAIN;
    }
    return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED;
}

框架的源碼寫的就是好,你一看名字就知道他想干嘛了!這里就進(jìn)入到最后一句,調(diào)用了一個 Advice 中到前置通知,來判斷權(quán)限是否滿足:

public boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute attr) {
    PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr;
    EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);
    Expression preFilter = preAttr.getFilterExpression();
    Expression preAuthorize = preAttr.getAuthorizeExpression();
    if (preFilter != null) {
        Object filterTarget = findFilterTarget(preAttr.getFilterTarget(), ctx, mi);
        this.expressionHandler.filter(filterTarget, preFilter, ctx);
    }
    return (preAuthorize != null) ? ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true;
}

現(xiàn)在,當(dāng)你看到這個 before 方法的時候,應(yīng)該會覺得比較熟悉了吧。

  1. 首先獲取到 preAttr 對象,這個對象里邊其實(shí)就保存著你 @PreAuthorize 注解中的內(nèi)容。
  2. 接下來跟進(jìn)當(dāng)前登錄用戶信息 authentication 創(chuàng)建一個上下文對象,此時創(chuàng)建出來的上下文對象中就包含了當(dāng)前用戶具備哪些權(quán)限。
  3. 獲取過濾器(我們這個項(xiàng)目中無)。
  4. 獲取到權(quán)限注解。
  5. 最后執(zhí)行表達(dá)式,去查看當(dāng)前用戶權(quán)限中是否包含請求所需要的權(quán)限。

就這樣,是不是很簡單?

到此這篇關(guān)于詳解Spring Security中權(quán)限注解的使用的文章就介紹到這了,更多相關(guān)Spring Security權(quán)限注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java判斷文件或者文件夾是否存在的功能(不存在創(chuàng)建,存在刪除)

    Java判斷文件或者文件夾是否存在的功能(不存在創(chuàng)建,存在刪除)

    在Java中,要判斷文件或文件夾是否存在,并根據(jù)判斷結(jié)果來執(zhí)行創(chuàng)建或刪除操作,你可以使用java.io.File類或者java.nio.file包中的類,本文介紹Java判斷文件或者文件夾是否存在,不存在創(chuàng)建,存在刪除,感興趣的朋友一起看看吧
    2025-03-03
  • java線程的基礎(chǔ)實(shí)例解析

    java線程的基礎(chǔ)實(shí)例解析

    這篇文章主要介紹了java線程的基礎(chǔ)實(shí)例解析,具有一定借鑒價值,需要的朋友可以參考下
    2017-12-12
  • 調(diào)用java.lang.Runtime.exec的正確姿勢分享

    調(diào)用java.lang.Runtime.exec的正確姿勢分享

    這篇文章主要介紹了調(diào)用java.lang.Runtime.exec的正確姿勢,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java IO流體系繼承結(jié)構(gòu)圖_動力節(jié)點(diǎn)Java學(xué)院整理

    Java IO流體系繼承結(jié)構(gòu)圖_動力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要介紹了Java IO流體系繼承結(jié)構(gòu)圖,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-05-05
  • 對spring task和線程池的深入研究

    對spring task和線程池的深入研究

    這篇文章主要介紹了對spring task和線程池的深入研究,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • spring中aop的xml配置方法實(shí)例詳解

    spring中aop的xml配置方法實(shí)例詳解

    AOP的配置方式有2種方式:xml配置和AspectJ注解方式。下面這篇文章主要給大家介紹了關(guān)于spring中aop的xml配置方法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-07-07
  • java之swing下拉菜單實(shí)現(xiàn)方法

    java之swing下拉菜單實(shí)現(xiàn)方法

    這篇文章主要介紹了java之swing下拉菜單實(shí)現(xiàn)方法,以實(shí)例形式較為詳細(xì)的分析了基于swing的下拉菜單實(shí)現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-09-09
  • Java面試最容易被刷的重難點(diǎn)之鎖的使用策略

    Java面試最容易被刷的重難點(diǎn)之鎖的使用策略

    鎖像synchronized同步塊一樣,是一種線程同步機(jī)制,但比Java中的synchronized同步塊更復(fù)雜。因?yàn)殒i(以及其它更高級的線程同步機(jī)制)是由synchronized同步塊的方式實(shí)現(xiàn)的,所以我們還不能完全擺脫synchronized關(guān)鍵字
    2021-10-10
  • springboot如何獲取文件流

    springboot如何獲取文件流

    這篇文章主要介紹了springboot如何獲取文件流,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java并發(fā)編程必備之Future機(jī)制

    Java并發(fā)編程必備之Future機(jī)制

    今天給大家?guī)淼氖顷P(guān)于Java并發(fā)編程的相關(guān)知識,文章圍繞著Java Future機(jī)制展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06

最新評論

宣化县| 泾川县| 威信县| 庆元县| 垦利县| 碌曲县| 阿尔山市| 南开区| 罗城| 凌海市| 彰化市| 中西区| 昌黎县| 墨竹工卡县| 尖扎县| 英超| 祁阳县| 新乡市| 金山区| 晴隆县| 郓城县| 彰化县| 莱西市| 蒙自县| 乐安县| 阿坝县| 广灵县| 淅川县| 南充市| 新营市| 怀仁县| 阿城市| 柳江县| 邓州市| 吉水县| 垦利县| 宝丰县| 江山市| 宁远县| 龙井市| 沐川县|