SpringSecurity整合JWT實現RABC權限系統的原理

 Spring Security 整合 JWT 實現前後端分離的鑑權

Spring Security 最核心的兩個功能就是驗證和鑑權。

3.1 Spring Security 的原理

認證就是驗證你是誰,鑑權就是你能幹什麼。權限系統的設計也必須按照這兩步來進行思考和設計。

Spring Security 本質就是一系列的攔截器組成的攔截器鏈。(如下圖)

在這裏插入圖片描述

3.2 Spring Boot 整合 Spring Security 的實現

3.2.1 認證

我們從上圖可以看出,認證跟 Spring Security 提供的 UserPasswordAuthenticationFilter 有關。

我們對這個類的源碼進行分析一下。

通過源碼我們可以看出,它主要攔截我們的登錄請求並拿到我們的登錄名和密碼,然後生成通過用戶名和密碼生成一個未認證的 UsernamePasswordAuthenticationToken 對象,等待我們驗證。就你登錄請求過來,我這個攔截器就會攔截到然後拿到你的用戶名、密碼,然後封裝成一個爲認證的東西,方便我後邊對你進行認證。

3.2.2 攔截器源碼分析

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
    // 你一按登錄就會請求認證
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 判斷是否爲POST方式的請求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            // 我們請求的登錄中獲取到用戶名和密碼
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            // 進一步驗證
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
}

這個未認證的 UsernamePasswordAuthenticationToken 的構造方法,我們通過源碼就知道它到底是個什麼東西。

UsernamePasswordAuthenticationToken 源碼:

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    //當前登錄的用戶名具有什麼權限,由於剛進行認證,這裏的權限我們賦值爲空
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    // 當前登錄的用戶名是否已通過驗證,由於這裏還沒進行認證,所以我們賦值爲爲認證
    this.setAuthenticated(false);
}

接着 AuthenticationManager 就會對我們上面的這個 UsernamePasswordAuthenticationToken 來進行認證。但 AuthenticationManager 並不是真正幹活的人,真正做事的是 ProviderManager。我們通過源碼可以看出 ProviderManager 繼承於我的 AuthenticationManager。然後它會選擇合適的 Provider 對我們的 UsernamePasswordAuthenticationToken 來進行認證。這個因爲是 UsernamePasswordAuthenticationToken,所以選擇的是 DaoAuthenticationProvider。

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var6 = this.getProviders().iterator();

        while(var6.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var6.next();
            // 1.判斷是否有provider支持該Authentication
            if (provider.supports(toTest)) {
                result = provider.authenticate(authentication);
            }
    }
}

開始對登錄請求進行認證。去調用自己實現的 UserDetailsService,返回 UserDetails。

對 UserDetails 的信息進行校驗,主要是帳號是否被凍結,是否過期等對密碼進行檢查,這裏調用了 PasswordEncoder 檢查 UserDetails 是否可用。這個時候就返回經過認證的 Authentication。

成功認證的話就會去調用我的 successHandler 返回,失敗就去調用我的 failHandler。

3.2.3 認證的總體思路流程

在這裏插入圖片描述

根據上面的思路,整個代碼來具體的講解一下是如何實現的。

首先我們要自定義 DaoAuthenticationProvider 對我們的 UsernamePasswordAuthenticationToken 來進行認證。我們在 SecurityConfig 中來進行定義(這裏需要對用戶名和密碼校驗)。

UserDetailsService:

在這裏插入圖片描述

在這裏插入圖片描述

認證成功和認證失敗的處理器:

在這裏插入圖片描述

認證成功生成 token 並返回:

在這裏插入圖片描述

認證:

在這裏插入圖片描述

拿到我的用戶名,看是否有這個用戶,並返回繼承了 UserDetails 格式的 User(此時還是未認證):

在這裏插入圖片描述

返回 jwtUser:

在這裏插入圖片描述

拿到密碼對密碼進行校驗:

在這裏插入圖片描述

加入認證通過,調用我的 successHandiler 處理類:

在這裏插入圖片描述

並將已認證對象 Authentication(這個類有用戶名,權限等信息)放入到我的 SecurityContextHolder 上下文中。這樣我們就可以在需要用到的時候從 SecurityContextHolder 中取出這個認證對象就好了。

3.2.4 認證前先校驗頭部是否有 token

如果不是調用的不是 login 登錄接口,我們就需要檢驗頭部是否有 token,就是第一次登錄後我們返回的認證標識。

那麼如何實現呢?因爲認證是經過我們的 UsernamePasswordAuthenticationFilter 攔截器,那麼我們可以在這個攔截器前先添加我的自定 JWT 攔截器來校驗頭部是否有 token。如果有就進行認證。

自定義的 JWT 攔截器:

在這裏插入圖片描述

將攔截器添加到我的 UsernamePasswordAuthenticationFilter 攔截器以實現先校驗頭部是否有 token,並進行認證。

在這裏插入圖片描述

3.2.5 鑑權

認證完後就要進行鑑權了。就是判斷當前用戶是否有訪問請求的 url 的權限。

思路:通過攔截器來判斷當前的請求的 url 是否哪些角色能夠訪問以及當前用戶是否有這些角色。如果匹配不上就鑑權失敗,匹配上就鑑權成功。

鑑權我們主要藉助於 Spring Security 的 FilterSecurityInterceptor 攔截器來進行。

根據資源表和資源角色表拿出訪問當前 url 需要的角色:

在這裏插入圖片描述

在這裏插入圖片描述

根據用戶表和角色表將當前用戶擁有的角色拿出來(用戶的角色我們已經在認證的時候就已經拿出來了)。

在這裏插入圖片描述

在這裏插入圖片描述

進行鑑權判斷(上圖中認證對象角色是我們在認證的時候已經放入的了。

這裏放入權限。這裏的 SecurityContexthold 是一個安全的上下文,我們將通過上面認證的 Authentication 對象會放在這裏面)。

在這裏插入圖片描述

最後將我們自定義的攔截器添加到 FilterSecurityInceptor 之前就可以進行認證。

在這裏插入圖片描述

JWT 的封裝:

在這裏插入圖片描述

 

 RABC 權限系統前置知識與項目整體把握

2.1 權限和角色和用戶的關係

在基於角色的 RABC 權限系統中,我們通過資源與角色的綁定,來確定哪些角色擁有哪些資源,然後在通過角色與用戶的綁定來確定哪些用戶是什麼角色,進而確定用戶有哪些資源的訪問權限。

在這裏插入圖片描述

所以我們在設計權限系統的表的時候就需要創建用戶表、角色表、用戶角色表、資源表、資源角色表。

用戶表

在這裏插入圖片描述

角色表

在這裏插入圖片描述

資源表

在這裏插入圖片描述

用戶角色表

在這裏插入圖片描述

角色資源表

在這裏插入圖片描述

在上面我用紅框框出來的是所有表的一個共性,在實際項目中就可以把它封裝成一個 BaseEntity 類,這個類會出現在我的項目的 core 子模塊下。對應地我們在執行 Dao 的插入、刪除操作的時候,也是需要將對共同部分的操作封裝成一個基礎操作,然後我們在編寫 dao、service、controller 的時候,對應的繼承纔是最正確的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章