登錄認證實現——實現原理筆記


登錄認證實現(springboot+vue)

1. 登錄認證相關介紹

登錄認證的整個過程有兩個部分組成,分別是用戶認證和權限認證。用戶認證是對用戶的相關登錄認證,權限認證是對已經登錄的用戶的操作權限識別與限制。

用戶認證:核對數據庫用戶名和密碼的相關信息是否正確。

權限認證:查看用戶是否具有相應的操作權限,例如:收到用戶修改信息的請求,會首先判斷用戶是會否具有ROLE_DELETE權限,如果沒有則告訴客戶端沒有訪問權限。

2. 准備工作

2.1 服務端

spring boot,spring security,JWT,token

2.2 客戶端

vuex,token,router

3. 服務端

3.1 spring security

spring security是一個基於spring aop 和servlet過濾器的安全框架。

詳細介紹鏈接: https://www.w3cschool.cn/springsecurity/

3.1.1 spring security的核心類

3.1.1.1 Authentication

用於用戶信息認證,用戶在進行登錄認證之前會將用戶的相關信息封裝在一個Authentication的實現類中,在登錄成功之后,又會將用戶權限(ROLE_USER)封裝到相關的Authentication實現類中,然后將其保存在SecurityContextHolder下的SecurityContext中,供后續的權限鑒定使用。

3.1.1.2 SecurityContextHolder

SecurityContextHolder是使用ThreadLocal來保存SecurityContext的,threadlocal在使用之后會自動清除(Security已經幫幫助我們做了)。當我們需要使用security中的用戶名是可以使用 Authentication.getPrincipal() 獲取當前用戶的信息 ,這里的Authentication是Authentication的對象。

3.1.1.3 AuthenticationManager 和 AuthenticationProvider

AuthenticationManager 是一個用於處理認證請求的接口,默認實現是 ProviderManager 。AuthenticationManger不會自己實現認證請求,而是委托給其所配置的AuthenticationProvider列表,然后使用每一個AuthenticationProvider進行認證。 如果有一個 AuthenticationProvider 認證后的結果不為 null,則表示該 AuthenticationProvider 已經認證成功,之后的 AuthenticationProvider 將不再繼續認證。

3.1.1.4 UserDetailsService(UserDetails很重要)

UserDetails是spring security中的一個核心接口,其中定義了一些可以獲取用戶名、密碼、權限等於認證有關的信息。 Spring Security 內部使用的 UserDetails 實現類大都是內置的 User 類,我們如果要使用 UserDetails 時也可以直接使用該類。在 Spring Security 內部很多地方需要使用用戶信息的時候基本上都是使用的 UserDetails,比如在登錄認證的時候。登錄認證的時候 Spring Security 會通過 UserDetailsService 的 loadUserByUsername() 方法獲取對應的 UserDetails 進行認證,認證通過后會將該 UserDetails 賦給認證通過的 Authentication 的 principal,然后再把該 Authentication 存入到 SecurityContext 中。之后如果需要使用用戶信息的時候就是通過 SecurityContextHolder 獲取存放在 SecurityContext 中的 Authentication 的 principal。

3.1.1.5 GrantedAuthority

Authentication 的 getAuthorities() 可以返回當前 Authentication 對象擁有的權限,即當前用戶擁有的權限。其返回值是一個 GrantedAuthority 類型的數組,每一個 GrantedAuthority 對象代表賦予給當前用戶的一種權限。GrantedAuthority 是一個接口,其通常是通過 UserDetailsService 進行加載,然后賦予給 UserDetails 的。

GrantedAuthority 中只定義了一個 get Authority() 方法,該方法返回一個字符串,表示對應權限的字符串表示,如果對應權限不能用字符串表示,則應當返回 null。

Spring Security 針對 GrantedAuthority 有一個簡單實現 SimpleGrantedAuthority。該類只是簡單的接收一個表示權限的字符串。Spring Security 內部的所有 AuthenticationProvider 都是使用 SimpleGrantedAuthority 來封裝 Authentication 對象。

3.1.2 spring security認證的過程

  1. 用戶使用用戶名和密碼進行登錄。
  2. Spring Security 將獲取到的用戶名和密碼封裝成一個實現了 Authentication 接口的 UsernamePasswordAuthenticationToken。
  3. 將上述產生的 token 對象傳遞給 AuthenticationManager 進行登錄認證。
  4. AuthenticationManager 認證成功后將會返回一個封裝了用戶權限等信息的 Authentication 對象。
  5. 通過調用 SecurityContextHolder.getContext().setAuthentication(...) 將 AuthenticationManager 返回的 Authentication 對象賦予給當前的 SecurityContext。

3.1.3 疑問

在request的時候,用戶的相關的信息是放在SecurityContext中,SecurityContext又放在LocalThread中,而LocalThread在request結束之后會自動清除,那下一次在request的時候不是有需要在進行一次用戶認證和鑒權嗎?

原來的解決辦法(session管理):使用session來存儲SecurityContext中的內容,每次訪問的時候再將session中的內容取出來放在SecurityContext中,結束之后再將SecurityContext中的內容再次放入session中。

上述辦法的弊端:首先我需要存儲session,其次,我需要解決服務器之間的session共享問題,還有CSRF安全問題等。

現在的解決辦法(token管理):不用存儲SecurityContext中的內容,自一次請求的時候生成token發送給客戶端,后面每次請求都在請求頭中帶上token,服務器判斷token之后,解析出用戶相關信息只有交予Security進行后續的操作。

3.2 token(令牌)

3.2.1 token是干啥的

token是在客戶端與服務端頻繁交互,服務端又和數據庫頻繁交互查詢用戶名和密碼確認身份,這樣的背景下產生的。它的使用減輕了服務器的壓力,較少了服務器與數據庫的交互,增強服務器的健壯性。

token是服務端生成的一串字符串,在為客戶端請求的令牌,在客戶端每次請求中都會帶上token,是服務端確認用戶的唯一標志,而不需要攜帶用戶名和密碼,也保證了安全性。此外 token還可以是無狀態的(什么是有狀態和無狀態?)

3.2.2 工作原理

登錄

業務請求

token過期刷新

以上處理過程都是有狀態的 ,需要在服務端做相關的token存儲的,和session沒啥區別,那怎么才能做到服務端不保存相關信息呢?——將所有的信息全部放到token里面。這樣只需要服務端確保是自己簽發的token就可以確認token有效,由於token的簽發和驗收都是服務端,所以可以使用對稱加密算法加以解決,由於不需要還原加密內容,所以使用散列算法——HMAC

3.2.3 疑問

上面說的這些個什么對稱加密算法,散列算法——HMAC什么的需要自己實現嗎?需要的的話那也太復雜了吧,我還不如乖乖用有狀態的呢?

答案是不用我們自己操作,因為有現成的開放標准供我們使用,是什么呢?

當然是JWT了,但有個問題使用JWT沒辦法處理過期刷新的問題,refreshToken需要使用redis來存儲。

注:使用redis來管理toekn(有狀態的,違背token的無狀態,但使用的時候經常有人JWT和redis一起使用,方便實現token刷新延期)

3.3 JWT

3.3.1 什么是JWT

jwt是json web token的簡稱,看簡稱就知道是實現token的一種解決方案。

jwt是為了在網絡應用環境之間傳遞聲明而執行的一種基於json的開放標准(RFC 7519)。這里的token是緊湊和安全的,很適合分布式站點的單點登錄場景,就是SSO

3.3.2 JWT組成

加密后的jwt是一個字符串,分別由Header,Payload,Signature三個部分組成,三個部分用“.”拼接在一起。

  • Header -由alg和type兩個部分組成,alg是加密類型,type=jwt是不變的,用於表示token是JWT類型的。

    Header:
    {
     "alg": "HS256",
     "typ": "JWT"
    }
    
  • Payload(Claims),JWT的主體部分,包含你想要的信息(自定義字段)

    Payload:
    {
     "sub": "19969139664",
     "name": "ygl",
     "admin": true
    }
    

    除了自定義字段之外,JWT還提供了七個默認字段:

    iss:發行人

    exp:到期時間

    sub:主題

    aud:用戶

    nbf:在此之前不可用

    iat:發布時間

    jti:JWT ID用於標識該JWT

    默認情況下JWT是未加密的,不要存放保密信息,防止信息泄露。

  • Signature,對Header,Payload的簽名。

    Signature:
    base64UrlEncode(Header) + "." + base64UrlEncode(Payload)
    

    base64Url算法是JWT序列化使用的算法,和常見的base64算法差不多

    作為令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三個字符是"+","/"和"=",由於在URL中有特殊含義,因此Base64URL中對他們做了替換:"="去掉,"+"用"-"替換,"/"用"_"替換,這就是Base64URL算法,很簡單把。
    

    簽名部分通過指定的算法生成哈希,確保數據不會被篡改。

    具體做法:

    指定一個密碼(secret),改密碼只有服務器知道,然后使用標頭中指定的的算法使用以下公式生成簽名。

    HMACSHA256(signature,secret)//signature是上面定義的Signature
    
  • 在計算出簽名哈希后,JWT頭,有效載荷和簽名哈希的三個部分組合成一個字符串,每個部分用"."分隔,就構成整個JWT對象。

3.3.3 JWT用法

整個的JWT對象就是我們用於認證的用戶信息的token,客戶端在收到JWT之后,將收好,不要丟失,下次請求的時候就帶上JWT對象,告訴服務端我是合法的,不能拒絕我。

4.客戶端

4.1 vuex

Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。

在這里主要用來管理token,刪除,設置等操作都在這里面。

4.2 storage

主要用到了localstorage作為本地存儲,存放token。

實例用法:

//訪問了當前域名下的本地 Storage 對象,並通過 Storage.setItem() 增加了一個數據項目。
localStorage.setItem('myCat', 'Tom');
//讀取 localStorage 項
let cat = localStorage.getItem('myCat');
//移除 localStorage 項
localStorage.removeItem('myCat');
//移除所有的 localStorage 項
localStorage.clear();

完整實例

4.3 路由導航守衛

這里主要用到的是全局前置守衛。

全局前置守衛: 當一個導航觸發時,全局前置守衛按照創建順序調用。守衛是異步解析執行,此時導航在所有守衛 resolve 完之前一直處於等待中。

在這里主要是用來攔截router,判斷用戶是否已經登錄(localstorage里面有沒有token),確定已經登錄才能做下一步操作,執行目標路由跳轉。(防止未登錄使用)

實例代碼:

router.beforeEach((to, from, next) => {
  ////////////////
  //添加自己的代碼
  ////////////////
});

4.4 token

收到token之后將token放在localstorage中存放,每次做路由跳轉的時候都會判斷token的狀態。放服務端發送請求的時候回在請求頭里添加token的信息,以便服務端能識別用戶。

token過期怎么和服務端同步?在使用中會有這樣一種情況,明顯token已經過期了,但是還是能做正常的路由跳轉,為啥呢?

只要客戶端不向服務端發送請求,客戶端就不會知道token是不是過期,它只要一看有token就會做出跳轉。

解決辦法:在全局路由中添加默認請求,來判斷token是否過期。如果不想每次router都做請求的話,可以做判斷,沒幾次路由做一次token核對。

4.5 請求攔截器($ajaxSetup())

作用?攔截客戶端發出的每一個請求。

這里用來干啥的?用來攔截請求向請求頭中添加token,注意再添加token的時候需要將原始token的字首去掉,字首具體是啥需要看服務端設置的是啥(我設置的是Bearer)

實例代碼:

$.ajaxSetup({
  dataType: "json",
  cache: false,
  headers: {
    "token": token
  },
  xhrFields: {
    withCredentials: true
  },
  complete: function (xhr) {
    if (xhr.code === 403) {//token過期,則跳轉到登錄頁面
      that.$router.push({path: '/login'});
    }
  }
});

注:第一次接觸做登錄認證,可能記錄的不一定完全正確。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM