中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring?Security中的權限注解如何使用

發布時間:2022-05-23 17:10:09 來源:億速云 閱讀:295 作者:iii 欄目:開發技術

今天小編給大家分享一下Spring Security中的權限注解如何使用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

Spring Security 權限注解的問題:

Spring?Security中的權限注解如何使用

1. 具體用法

先來看看 Spring Security 權限注解的具體用法,如下:

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

類似于上面這樣,意思就是說,當前用戶需要具備 tienchin:channel:query 權限,才能執行當前的接口方法。

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

  • 首先明白 Spring 中提供的 SpEL。

  • 其次搞明白 Spring Security 中對方法注解的處理規則。

我們一個一個來看。

2. SpEL

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

SpEL 給 Spring 社區提供一種簡單而高效的表達式語言,一種可貫穿整個 Spring 產品組的語言。這種語言的特性基于 Spring 產品的需求而設計,這是它出現的一大特色。

在我們離不開 Spring 框架的同時,其實我們也已經離不開 SpEL 了,因為它太好用、太強大了,SpEL 在整個 Spring 家族中也處于一個非常重要的位置。但是很多時候,我們對它的只了解一個大概,其實如果你系統的學習過 SpEL,那么上面 Spring Security 那個注解其實很好理解。

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

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

代碼如下:

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

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

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

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

這個打印結果為 3。

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

不過很多時候,我們要執行的表達式可能比較復雜,這時候上面這種調用方式就不太夠用了。

此時我們可以為要調用的表達式設置一個上下文環境,這個時候就會用到 EvaluationContext 或者它的子類,如下:

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

當然上面這個表達式不需要設置上下文環境,我舉一個需要設置上下文環境的例子。

例如我現在有一個 User 類,如下:

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

現在我的表達式是這樣:

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);

這個表達式就表示獲取 user 對象的 username 屬性。將來創建一個 user 對象,放到 StandardEvaluationContext 中,并基于此對象執行表達式,就可以打印出來想要的結果。

如果我們將 user 對象設置為 rootObject,那么表達式中就不需要 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);

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

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

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

我們就可以通過表達式調用這兩個方法,如下:

調用有參的 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);

就直接寫方法名然后執行就行了。

調用無參的 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);

這些就都好懂了。

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

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

然后通過 SpEL 表達式來調用這個名為 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);
}

給配置的上下文環境設置一個 bean 解析器,這個 bean 解析器會自動跟進名字從 Spring 容器中找打響應的 bean 并執行對應的方法。

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

3. @PreAuthorize

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

權限的實現方式千千萬,又有各種不同的權限模型,然而歸結到代碼上,無非兩種:

基于 URL 地址的權限處理

基于方法注解的權限處理

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

@PreAuthorize 注解當然對應的是后者。這次做的 tienchin 項目就是后者,我們來看一個例子:

@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,對應的類位于 org.javaboy.tienchin.framework.web.service.PermissionService 中。

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

這個 hasPermi 方法的邏輯其實很簡單:

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));
}

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

那么這個方法是在哪里調用的呢?

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

在投票器中,我們可以看到專門處理 @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;
}

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

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;
}

現在,當你看到這個 before 方法的時候,應該會覺得比較熟悉了吧。

  1. 首先獲取到 preAttr 對象,這個對象里邊其實就保存著你 @PreAuthorize 注解中的內容。

  2. 接下來跟進當前登錄用戶信息 authentication 創建一個上下文對象,此時創建出來的上下文對象中就包含了當前用戶具備哪些權限。

  3. 獲取過濾器(我們這個項目中無)。

  4. 獲取到權限注解。

  5. 最后執行表達式,去查看當前用戶權限中是否包含請求所需要的權限。

以上就是“Spring Security中的權限注解如何使用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

浙江省| 鄱阳县| 红安县| 项城市| 仁布县| 长子县| 施秉县| 临武县| 金山区| 运城市| 三亚市| 巩义市| 荔波县| 光泽县| 北碚区| 松桃| 双城市| 工布江达县| 铁力市| 雅安市| 阜城县| 姜堰市| 麻城市| 邹平县| 永福县| 开远市| 关岭| 习水县| 汝城县| 东山县| 荥阳市| 宁安市| 武清区| 巴南区| 鸡东县| 静宁县| 杨浦区| 孝义市| 青神县| 阿巴嘎旗| 吉木乃县|