shiro源碼篇 - shiro的filter,你值得擁有

来源:https://www.cnblogs.com/youzhibing/archive/2018/12/06/9970405.html
-Advertisement-
Play Games

前言 開心一刻 已經報廢了一年多的電腦,今天特麽突然開機了,嚇老子一跳,只見電腦管家緩緩地出來了,本次開機一共用時一年零六個月,打敗了全國0%的電腦,電腦管家已經對您的電腦失去信心,然後它把自己卸載了,只剩我在一旁發呆。 路漫漫其修遠兮,吾將上下而求索! github:https://github. ...


前言

  開心一刻

    已經報廢了一年多的電腦,今天特麽突然開機了,嚇老子一跳,只見電腦管家緩緩地出來了,本次開機一共用時一年零六個月,打敗了全國0%的電腦,電腦管家已經對您的電腦失去信心,然後它把自己卸載了,只剩我在一旁發呆。

  路漫漫其修遠兮,吾將上下而求索!

  github:https://github.com/youzhibing

  碼雲(gitee):https://gitee.com/youzhibing

shiroFilter的註冊

  此篇博文講到了springboot的filter註冊,但只是filter註冊的一種方式:通過FilterRegistrationBean實現。而Shiro Filter的註冊是採用另外的方式實現的,我們接著往下看

  ShiroFilterFactoryBean實現了FactoryBean,我們來看下ShiroFilterFactoryBean對FactoryBean的getObject方法的實現

git圖一

    可以看到createInstance()返回的是SpringShiroFilter的實例;SpringShiroFilter的類圖如下

  getObject方法只是創建了一個SpringShiroFilter實例,並註冊到了spring容器中,那是如何註冊到servlet容器的呢?我們來跟下源碼

    這篇博文其實涉及到了,只是那時候沒細講,我們還是從那裡開始

gif圖二

    ServletContextInitializerBeans.java的構造方法,裡面有addServletContextInitializerBeans(beanFactory)方法和addAdaptableBeans(beanFactory),我們來好好瞅一瞅

    addServletContextInitializerBeans(beanFactory)

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
    for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
            beanFactory, ServletContextInitializer.class)) {
        addServletContextInitializerBean(initializerBean.getKey(),
                initializerBean.getValue(), beanFactory);
    }
}


private void addServletContextInitializerBean(String beanName,
        ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
    if (initializer instanceof ServletRegistrationBean) {
        Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
        addServletContextInitializerBean(Servlet.class, beanName, initializer,
                beanFactory, source);
    }
    else if (initializer instanceof FilterRegistrationBean) {
        Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
        addServletContextInitializerBean(Filter.class, beanName, initializer,
                beanFactory, source);
    }
    else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
        String source = ((DelegatingFilterProxyRegistrationBean) initializer)
                .getTargetBeanName();
        addServletContextInitializerBean(Filter.class, beanName, initializer,
                beanFactory, source);
    }
    else if (initializer instanceof ServletListenerRegistrationBean) {
        EventListener source = ((ServletListenerRegistrationBean<?>) initializer)
                .getListener();
        addServletContextInitializerBean(EventListener.class, beanName, initializer,
                beanFactory, source);
    }
    else {
        addServletContextInitializerBean(ServletContextInitializer.class, beanName,
                initializer, beanFactory, initializer);
    }
}
View Code

      會將spring bean工廠(beanFactory)中類型是ServletContextInitalizer類型的實例(包括ServletRegistrationBean、FilterRegistrationBean、DelegatingFilterProxyRegistrationBean、ServletListenerRegistrationBean)添加進ServletContextInitializerBeans的initializers屬性中。

private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;

      ServletContextInitalizer的子類圖如下所示

      這也就是上篇博文註冊Filter的方式,以RegistrationBean方式實現的,但是SpringShiroFilter不是在這添加進ServletContextInitializerBeans的initializers中的哦

    addAdaptableBeans(beanFactory)

private void addAdaptableBeans(ListableBeanFactory beanFactory) {
    MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
    addAsRegistrationBean(beanFactory, Servlet.class,
            new ServletRegistrationBeanAdapter(multipartConfig));
    addAsRegistrationBean(beanFactory, Filter.class,
            new FilterRegistrationBeanAdapter());
    for (Class<?> listenerType : ServletListenerRegistrationBean
            .getSupportedTypes()) {
        addAsRegistrationBean(beanFactory, EventListener.class,
                (Class<EventListener>) listenerType,
                new ServletListenerRegistrationBeanAdapter());
    }
}
View Code

      會將beanFactory中的Servlet、Filter、Listener實例封裝成對應的RegistrationBean,然後添加到ServletContextInitializerBeans的initializers;部分細節沒去跟,有興趣的可以自行去跟下源代碼。

    ServletContextInitializerBeans的sortedList的內容最終如下

    然後遍歷這個sortedList,逐個註入到servlet容器

  小結

    springboot下有3種方式註冊Filter(Servlet、Listener類似),FilterRegistrationBean、@WebFilter 和@Bean,@WebFilter我還沒試過,另外這3種方式註冊的Filter的優先順序是:FilterRegistrationBean > @WebFilter > @Bean(網上查閱的資料,我沒試哦,使用過程中需要註意!)。

    不管是FilterRegistrationBean方式、@WebFilter方式,還是@Bean方式,只要是受spring容器管理,最終都會添加到ServletContextInitializerBeans的initializers中,都會成功註冊到servlet容器。@WebFilter方式和@Bean方式註冊的Filter都會被封裝成FilterRegistrationBean,然後添加進ServletContextInitializerBeans的initializers;3中方式最終殊途同歸,都以FilterRegistrationBean的形式存在ServletContextInitializerBeans的initializers中。SpringShiroFilter的註冊算是@Bean方式註冊的,至此SpringShiroFilter就註冊到了servlet容器中了。

    ServletContextInitializerBeans的sortedList如下:

private List<ServletContextInitializer> sortedList;

    是一個有序的ServletContextInitializer List,這個有序針對的同類型,比如所有的FilterRegistrationBean有序,所有的ServletRegistrationBean有序,FilterRegistrationBean與ServletRegistrationBean之間有沒有序是無意義的。

shiro中的Filter鏈

  shiro的預設filter列表

    除了SpringShiroFilter之外,shiro還有預設的11個Filter;細心的朋友應該在git圖一中已經發現了,在創建DefaultFilterChainManager,就把預設的11個Filter添加到它的filters中

private Map<String, Filter> filters; //pool of filters available for creating chains

private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain

public DefaultFilterChainManager() {
    this.filters = new LinkedHashMap<String, Filter>();
    this.filterChains = new LinkedHashMap<String, NamedFilterList>();
    addDefaultFilters(false);    // 將預設的11個Filter添加到filters
}

    這11個Filter具體如下

anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);

  Filter鏈

    SpringShiroFilter註冊到servlet容器中,請求肯定會經過SpringShiroFilter的doFilter方法,我們就從此開始跟一跟源代碼

gif圖三

    上圖中可能展示的不夠細,主要就是兩點:1、路徑匹配:pathMatches(pathPattern, requestURI),預設的Fliter逐個與請求URI進行匹配;2、代理FilterChain:ProxiedFilterChain。如果匹配不上,那麼直接走servlet的FilterChain,否則先走shiro的代理FilterChain(ProxiedFilterChain),之後再走servlet的FilterChain。

    ProxiedFilterChain源代碼如下

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.shiro.web.servlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;
import java.util.List;

/**
 * A proxied filter chain is a {@link FilterChain} instance that proxies an original {@link FilterChain} as well
 * as a {@link List List} of other {@link Filter Filter}s that might need to execute prior to the final wrapped
 * original chain.  It allows a list of filters to execute before continuing the original (proxied)
 * {@code FilterChain} instance.
 *
 * @since 0.9
 */
public class ProxiedFilterChain implements FilterChain {

    //TODO - complete JavaDoc

    private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class);

    private FilterChain orig;                    // 原FilterChain,也就是servlet容器的FilterChain
    private List<Filter> filters;                // shiro預設的11個Filter
    private int index = 0;

    public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
        if (orig == null) {
            throw new NullPointerException("original FilterChain cannot be null.");
        }
        this.orig = orig;
        this.filters = filters;
        this.index = 0;
    }

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            this.orig.doFilter(request, response);                        // 當shiro的11個Filter走完之後,繼續走servlet容器的FilterChain
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);    // 遞歸逐個走shiro的11個Filter
        }
    }
}
View Code

    Shiro對Servlet容器的FilterChain進行了代理,即ShiroFilter在繼續Servlet容器的Filter鏈的執行之前,通過ProxiedFilterChain對Servlet容器的FilterChain進行了代理;即先走Shiro自己的Filter體系,然後才會委托給Servlet容器的FilterChain進行Servlet容器級別的Filter鏈執行;Shiro的ProxiedFilterChain執行流程:1、先執行Shiro自己的Filter鏈;2、再執行Servlet容器的Filter鏈(即原始的 Filter)。

總結

  1、SpringShiroFilter註冊到spring容器,會被包裝成FilterRegistrationBean,通過FilterRegistrationBean註冊到servlet容器;

  2、一般而言,shiro的PathMatchingFilterChainResolver會匹配所有的請求,Shiro對Servlet容器的FilterChain進行了代理,生成代理FilterChain:ProxiedFilterChain,請求先走Shiro自己的Filter鏈,再走Servelt容器的Filter鏈;

  3、題外話,springboot註冊Filter、Servlet、Listener方式類似,都有3種,具體是哪三種,大家去上文看;關於Shiro的Filter,本文沒做更詳細的講解,需要瞭解的可以去看《跟我學shiro》

參考

  《跟我學shiro》

  shiro源碼


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 系統架構設計師-軟體水平考試高級-理論-電腦網路。其中涉及TCP/IP協議族,網路規劃與設計,網路接入,網路存儲,綜合佈線,物聯網,雲計算等。 ...
  • 模板方法模式(Template Method Pattern)是一種簡單的、常見的且應用非常廣泛的模式。 定義: 定義一個操作中演算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。 模板方法模式的類圖如下所示。 模板方法模式涉及兩個角色: 抽象模板( ...
  • 海康&大華&DSS獲取RTSP 實時流 海康:rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream說明:username: 用戶名。例如admin。password: 密碼。例如12345。ip: ...
  • 命令模式(Command Pattern)又稱為行動(Action)模式或交易(Transaction)模式。 定義: 將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數化,對請求排隊或記錄請求日誌,可以提供命令的撤銷和恢復功能。 命令模式類圖如下所示。 命令模式中有如下4個角色。 命令( ...
  • 搬到小機房後終於能用VSCode啦(~~沒錯以前的系統是xp~~) 但是這東西比Dev難搞多了qwq,簡單記一下自己的DIY歷程吧(~~不然全搞炸就涼了~~) 設置語言為中文 可以直接下載插件 讓VSCode支持編譯C++程式 首先要有MingW,一個很simple的方法是直接把DevC++的Min ...
  • 代理模式是一種很常用的模式,JDK也內置了對於代理的支持,動態代理,本文對代理模式進行了介紹,意圖,結構,java實現,對靜態代理和動態代理進行了分析,並且給出了代碼示例,並且介紹了CGLIB的使用。 ...
  • 外觀模式又稱為門面模式Facade是一種簡單的設計模式,但是他背後的思想為迪米特原則,理解門面模式更有助於理解迪米特原則--不要和陌生人說話的原則,可以降低系統的耦合程度,本文介紹了外觀模式的意圖,結構,並且給出了java代碼示例。 ...
  • List 是 Java 開發中經常會使用的集合,你們知道有哪些方式可以初始化一個 List 嗎?這其中不缺乏一些坑,今天棧長我給大家一一普及一下。 1、常規方式 這種就是我們平常用的最多最平常的方式了,沒什麼好說的,後面缺失的泛型類型在 JDK 7 之後就可以不用寫具體的類型了,改進後會自動推斷類型 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...