SpringSecurity整合JWT-詳細(xì)版

說明

  1. 這是一個比較詳盡的SpringSecurity整合JWT的例子(代碼直接可以運(yùn)行,關(guān)鍵代碼都有很詳細(xì)的注釋)
  2. 本文并沒有使用spring oauth2,不要搞混
  3. 本文中的原理解釋只是大概的介紹,在代碼中有非常多的注釋,配合本文食用更佳
    DEMO地址 https://github.com/nixuechao/security-jwt

運(yùn)行demo

什么也不需要配置,直接運(yùn)行就行,提供了一個testController,運(yùn)行期望如下

   /**任何人都能訪問
     * @return
     */
    @GetMapping("/publicMsg")
    public String getMsg(){
        return "you get the message!";
    }

    /**登錄的用戶才能訪問
     * @return
     */
    @GetMapping("/innerMsg")
    public String innerMsg(){
        return "you get the message!";
    }

    /**管理員(admin)才能訪問
     * @return
     */
    @GetMapping("/secret")
    public String secret(){
        return "you get the message!";
    }

登錄流程-關(guān)鍵類介紹

  • JwtLoginFilter 自定義的登錄過濾器,把它加到SpringSecurity的過濾鏈中,攔截登錄請求它干的事有

    1. 設(shè)置登錄的url,請求的方式,其實(shí)也就是定義這個過濾器要攔截哪個請求

    2. 調(diào)用JwtAuthenticationProvider進(jìn)行登錄校驗(yàn)

    3. 校驗(yàn)成功調(diào)用LoginSuccessHandler,校驗(yàn)失敗調(diào)用LoginSuccessHandler


  • JwtAuthenticationProvider 自定義的認(rèn)證器,賬號密碼對不對等校驗(yàn)就是它干的,主要功能

    1. 首先規(guī)定自己支持校驗(yàn)?zāi)欠N憑證(Authentication)

    2. 進(jìn)行用戶校驗(yàn),調(diào)用JwtUserDetailServiceImpl 查詢當(dāng)前用戶(JwtUser),判斷用戶賬號密碼是否正確,用戶是否過期,被鎖定等等

    3. 若用戶校驗(yàn)失敗則拋異常給JwtLoginFilter,JwtLoginFilter捕獲異常調(diào)用登錄失敗的處理類(LoginFailureHandler)

    4. 若用戶校驗(yàn)成功,則生成一個已認(rèn)證的憑證,也就是Authentication,對應(yīng)本例的JwtLoginToken 并返回給JwtLoginFilter,JwtLoginFilter拿到憑證后調(diào)用登陸成功的處理類LoginSuccessHandler


  • JwtLoginToken 它就是上面說的憑證,繼承自Authentication

    1. 保存當(dāng)前用戶的認(rèn)證信息,如認(rèn)證狀態(tài),用戶名密碼,擁有的權(quán)限等

  • JwtUser 用戶實(shí)體,實(shí)現(xiàn)UserDetails,UserDetails為springSecurity默認(rèn)的用戶實(shí)體抽象

    1. 主要需要實(shí)現(xiàn)UserDetails的幾個方法,如獲取用戶名,密碼,獲取用戶凍結(jié)狀態(tài)等

  • JwtUserDetailServiceImpl UserDetailsService的實(shí)現(xiàn),提供根據(jù)用戶名查詢用戶信息的功能
    JwtAuthenticationProvider在進(jìn)行登錄信息校驗(yàn)時就會通過它查詢用戶信息


  • LoginFailureHandler 登錄失敗的處理類,被JwtLoginFilter調(diào)用,JwtLoginFilter捕獲到異常,就會調(diào)用它,并且把異常信息傳給它


  • LoginSuccessHandler 登錄成功的處理類,被JwtLoginFilter調(diào)用,并把JwtAuthenticationProvider創(chuàng)建的憑證(JwtLoginToken)傳給它,它就可以根據(jù)憑證里的認(rèn)證信息進(jìn)行登錄成功的處理,如生成token等

token校驗(yàn)-關(guān)鍵類介紹

在登錄過程中,登錄成功,調(diào)用LoginSuccessHandler生成了token返回給前端,那么登錄成功后訪問其他路徑,如何根據(jù)token進(jìn)行權(quán)限校驗(yàn)?zāi)?/p>

  • JwtKeyConfig 自定義的一個配置類,配置jwt,我這里的簽名驗(yàn)證用的是RSA加密,在這里配置了密鑰對


  • JwtHeadFilter 實(shí)現(xiàn)token校驗(yàn)的核心,這是自定義的過濾器,主要是請求通過過濾器時,會對其攜帶的token進(jìn)行解析和校驗(yàn)

    1. 獲取請求中攜帶的token
    2. 若沒有獲取到token則return,調(diào)交給接下來的過濾器鏈處理
    3. 若有token,但是校驗(yàn)失敗,進(jìn)行校驗(yàn)失敗處理
    4. 若token校驗(yàn)成功,通過從token中獲取的用戶信息生成一個憑證(Authentication),并放置到SecurityContext

在上面的2中沒有獲取到token為什么這么處理,首先springSecurity判斷用戶是否認(rèn)證成功的標(biāo)志是SecurityContext中是否有憑證(Authentication),在過濾鏈中,最后部分有一個匿名過濾器(AnonymousAuthenticationFilter),請求經(jīng)過這個過濾器,若SecurityContext中沒有憑證,會被設(shè)置一個匿名憑證.

最后決定請求是否通過的過濾器是FilterSecurityInterceptor,它會調(diào)用WebExpressionVoter來決定當(dāng)前用戶是否是否有權(quán)限訪問url,若沒有權(quán)限就會拋出AccessDeniedException,當(dāng)拋出這個異常時就會有兩種處理條件,若SecurityContext 中的憑證是匿名的就表示請求中沒有token,需要登錄,若憑證不是匿名的就表示當(dāng)前用戶沒有權(quán)限訪問次URL.

上面這個判斷邏輯發(fā)生在ExceptionTranslationFilter過濾器中,拋出異常時對應(yīng)的操作可以在WebSecurityConfigurerAdapter中的configure方法中配置

......
http
                //身份驗(yàn)證入口,當(dāng)需要登錄卻沒登錄時調(diào)用
                //具體為,當(dāng)拋出AccessDeniedException異常時且當(dāng)前是匿名用戶時調(diào)用
                //匿名用戶: 當(dāng)過濾器鏈走到匿名過濾器(AnonymousAuthenticationFilter)時,
                //會進(jìn)行判斷SecurityContext是否有憑證(Authentication),若前面的過濾器都沒有提供憑證,
                //匿名過濾器會給SecurityContext提供一個匿名的憑證(可以理解為用戶名和權(quán)限為anonymous的Authentication),
                //這也是JwtHeadFilter發(fā)現(xiàn)請求頭中沒有jwtToken不作處理而直接進(jìn)入下一個過濾器的原因
            .exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("需要登陸");
        })

                //拒絕訪問處理,當(dāng)已登錄,但權(quán)限不足時調(diào)用
                //拋出AccessDeniedException異常時且當(dāng)不是匿名用戶時調(diào)用
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().write("沒有權(quán)限");
                })
                ......

轉(zhuǎn)載請注明出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容