[Shiro] filter&realm綁定 多登陸入口斤富,區(qū)分前后臺

Shiro框架 filter&realm綁定

這種實現(xiàn)方式有問題葵诈,會影響到后續(xù)的角色驗證。不建議看了

新的實現(xiàn)方式 : 自定義Token


  • shiro filter與Realm綁定
  • 使用Spring整合Shiro
  • 多登陸入口劲适,成功后跳轉到不同地址
  • 重寫Realm獲取方式

最近在項目中使用了apache的輕量級Shiro框架進行權限管理楷掉,使用了多個filter,Shiro會默認迭代Realm進行登陸减响,即使第一個Realm通過校驗靖诗,也會繼續(xù)迭代,造成性能的浪費支示,和數(shù)據(jù)庫查詢刊橘。又或者前Realm里做了一些校驗,拋出了異常颂鸿,也會被后面的無用的Realm校驗失敗拋出的一場覆蓋促绵。


不廢話,上教程不廢話嘴纺,上教程
注意:這里filter統(tǒng)一指shiro 定義的filter
至于servlet的filter會寫成 servlet filter

這是先前的配置

已經(jīng)實現(xiàn)方式賦予Realm對應的role角色败晴,已經(jīng)有多入口,前后臺分開的功能栽渴,

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- shiro 的核心安全接口 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 未授權時要跳轉的連接 -->
        <property name="unauthorizedUrl" value="/unauthorized.jsp" />
        <property name="filters">
            <map>
                <entry key="admin" value-ref="adminformAuthenticationFilter" />
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </map>
        </property>
        <!-- shiro 連接約束配置 -->
        <property name="filterChainDefinitions">
            <value>
                /user.html = authc
                /admin.html =authc
                /login.html = authc
                /admin/login.html =admin
                /logout = logout
                /resource/** = anon
            </value>
        </property>
    </bean>

    <bean id="formAuthenticationFilter"
        class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <property name="loginUrl" value="/login.html" />
        <property name="successUrl" value="/ok.html" />
    </bean>
    <bean id="adminformAuthenticationFilter"
        class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <property name="loginUrl" value="/admin/login.html" />
        <property name="successUrl" value="/admin/ok.html" />
    </bean>
    <bean id="AdminRealm" class="cc.yihy.realm.AdminRealm">
        <property name="userService" ref="UserService" />
    </bean>
    <bean id="UserRealm" class="cc.yihy.realm.UserRealm">
        <property name="userService" ref="UserService" />
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- 默認的Realm驗證 -->
        <property name="realms">
            <list>
                <ref bean="UserRealm" />
                <ref bean="AdminRealm"></ref>
            </list>
        </property>
    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

在我們點擊登陸后尖坤,Shiro會使用我們配置的FormAuthenticationFilter進行驗證。

執(zhí)行

//父類里實現(xiàn)的方法
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    //這里創(chuàng)建用戶的令牌
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            //在這里進行登陸驗證
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

      protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String username = getUsername(request);
        String password = getPassword(request);
        return createToken(username, password, request, response);
    }
    //父類的創(chuàng)建令牌的實現(xiàn)
     protected AuthenticationToken createToken(String username, String password,
                                              ServletRequest request, ServletResponse response) {
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        return createToken(username, password, rememberMe, host);
    }
    //父類的創(chuàng)建令牌的實現(xiàn)
   protected AuthenticationToken createToken(String username, String password,
                                              boolean rememberMe, String host) {
        return new UsernamePasswordToken(username, password, rememberMe, host);
    }

subject.login(token);的實現(xiàn)類里闲擦,又調(diào)用了安全管理器的login慢味。傳的對象是一個Token令牌。

再看看安全管理器里面是怎么進行登錄的

Subject subject = securityManager.login(this, token);

 //安全管理器實現(xiàn)類DefaultSecurityManager里面的login
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
      //
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
            throw ae; //propagate
        }

        Subject loggedIn = createSubject(token, info, subject);

        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }
  //這是它的父類里面 
   private Authenticator authenticator;

   public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
       //真正進行登陸Realm的在這里
        return this.authenticator.authenticate(token);
    }

還是看下Authenticator接口實現(xiàn)類(ModularRealmAuthenticator)的源碼

 這是配置的Realm集合
private Collection<Realm> realms;
//迭代Realm進行驗證
   protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }

到這里墅冷,filter調(diào)用realm進行驗證的流程基本已經(jīng)清晰了纯路。

filter創(chuàng)建一個Token對象,傳到安全管理器(securityManager)寞忿,讓安全管理器調(diào)用Authenticator屬性進行迭代Realm驰唬。
怎么讓Filter找到對應權限的realm呢?他們中間傳遞一一個對象Token令牌腔彰。
Token里放了這些基本信息
new UsernamePasswordToken(username, password, rememberMe, host)叫编;

從filter到realm并沒有角色名的傳遞。
filter的name屬性里放的是角色名霹抛。這點在Spring創(chuàng)建ShiroFilter 可以看到宵溅。

//getFilters()拿到的是xml配置上的filters
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                //'init' argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
                manager.addFilter(name, filter, false);
            }
        }

現(xiàn)在想要把filter的信息傳給realm就需要在他們之間傳遞的對象動手腳了。讓Token把Filter對象帶過去就行了上炎。
那那邊怎么處理呢。

實例安全管理器的時候,會實例一個ModularRealmAuthorizer對象藕施,并可以設置為我們自定義的寇损。

 private Authorizer authorizer;
//安全管理器實現(xiàn)類的父類,在實例安全管理器的時候裳食,會創(chuàng)建一個ModularRealmAuthorizer對象矛市,迭代Realm驗證,就是ModularRealmAuthorizer做的工作
    /**
     * Default no-arg constructor that initializes an internal default
     * {@link org.apache.shiro.authz.ModularRealmAuthorizer ModularRealmAuthorizer}.
     */
    public AuthorizingSecurityManager() {
        super();
        this.authorizer = new ModularRealmAuthorizer();
    }

   public void setAuthorizer(Authorizer authorizer) {
        if (authorizer == null) {
            String msg = "Authorizer argument cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        this.authorizer = authorizer;
    }

到這里诲祸,F(xiàn)ilter&Realm綁定需要修改的地方基本都知道了浊吏。
重寫FormAuthenticationFilter的createToken(String username, String password,boolean rememberMe, String host)方法自定義一個Token救氯,可以存放Filter找田。重寫ModularRealmAuthorizer對象中獲取Realm的方法。

下面是實現(xiàn)代碼

NewFormAuthenticationFilter 實現(xiàn)

public class NewFormAuthenticationFilter extends FormAuthenticationFilter {
    /**
     * 創(chuàng)建自定義的令牌着憨,加入當前filter
     */
    @Override
    protected AuthenticationToken createToken(String username, String password,
            boolean rememberMe, String host) {
        return new UsernamePasswordAndFilterToken(username, password,
                rememberMe, host, this);
    }

    /**
     * 獲取當前Filter的名字(角色名)擴大訪問范圍
     */
    @Override
    public String getName() {
        return super.getName();
    }

}

UsernamePasswordAndFilterToken 實現(xiàn)

public class UsernamePasswordAndFilterToken extends UsernamePasswordToken {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    /**
     * 存放當前Filter
     */
    private NameableFilter loginFilter;

    public UsernamePasswordAndFilterToken() {
        super();
    }

    public UsernamePasswordAndFilterToken(final String username,
            final char[] password, final boolean rememberMe, final String host,
            final AdviceFilter loginFilter) {
        super(username, password, rememberMe, host);
        this.loginFilter = loginFilter;
    }

    public UsernamePasswordAndFilterToken(final String username,
            final String password, final boolean rememberMe, final String host,
            final AdviceFilter loginFilter) {
        super(username, password, rememberMe, host);
        this.loginFilter = loginFilter;
    }

    public NameableFilter getLoginFilter() {
        return loginFilter;
    }

    public void setLoginFilter(NameableFilter loginFilter) {
        this.loginFilter = loginFilter;
    }
}

DefineModularRealmAuthenticator 實現(xiàn)

public class DefineModularRealmAuthenticator extends ModularRealmAuthenticator {

    private static final Logger log = LoggerFactory
            .getLogger(DefineModularRealmAuthenticator.class);
    
    private Map<String, Realm> defineRealms;

    /**
     * 判斷Realm是不是null
     */
    @Override
    protected void assertRealmsConfigured() throws IllegalStateException {
        defineRealms = getDefineRealms();
        if (CollectionUtils.isEmpty(defineRealms)) {
            String msg = "Configuration error:  No realms have been configured!  One or more realms must be "
                    + "present to execute an authentication attempt.";
            throw new IllegalStateException(msg);
        }
    }
    /**
     * 根據(jù)filter的name 取對應realm進行登錄
     */
    @Override
    protected AuthenticationInfo doAuthenticate(
            AuthenticationToken authenticationToken)
            throws AuthenticationException {
        assertRealmsConfigured();
    
        /**
         * authenticationToken 如果是自定義的UsernamePasswordAndFilterToken墩衙,調(diào)用單個realm
         * 否則 使用默認的迭代realm方式
         */
        if(authenticationToken instanceof UsernamePasswordAndFilterToken){
                NewFormAuthenticationFilter loginFilter = (NewFormAuthenticationFilter)((UsernamePasswordAndFilterToken) authenticationToken).getLoginFilter();

                Realm realm = defineRealms.get(loginFilter.getName());
                if(realm==null){
                    log.error("沒有配置NewFormAuthenticationFilter對應的Realm");
                    throw new RuntimeException("沒有配置NewFormAuthenticationFilter對應的Realm");
                };
                return doSingleRealmAuthentication(realm,authenticationToken);
        }else {
            return oldDoAuthenticate(authenticationToken);
        }
            

    }

    private  AuthenticationInfo oldDoAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException{
        
        Collection<Realm> realms = defineRealms.values();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
    
    public void setDefineRealms(Map<String, Realm> defineRealms) {
        this.defineRealms = defineRealms;
    }

    public Map<String, Realm> getDefineRealms() {
        return defineRealms;
    }
}

在xml中的配置修改為

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- shiro 的核心安全接口 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 未授權時要跳轉的連接 -->
        <property name="unauthorizedUrl" value="/unauthorized.jsp" />
        <property name="filters">
            <map>
                <entry key="admin" value-ref="adminformAuthenticationFilter" />
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </map>
        </property>
        <!-- shiro 連接約束配置 -->
        <property name="filterChainDefinitions">
            <value>
                /user.html = authc
                /admin.html =authc
                /login.html = authc
                /admin/login.html =admin
                /logout = logout
                /resource/** = anon
            </value>
        </property>
    </bean>
    <!-- filter&realm綁定 -->
    <bean id="DefineModularRealmAuthenticator"
        class="cc.yihy.shiro.authc.pam.DefineModularRealmAuthenticator">
        <property name="defineRealms">
            <map>
                <entry key="authc" value-ref="UserRealm" />
                <entry key="admin" value-ref="AdminRealm" />
            </map>
        </property>
    </bean>
    <bean id="formAuthenticationFilter"
        class="cc.yihy.shiro.filter.NewFormAuthenticationFilter">
        <property name="loginUrl" value="/login.html" />
        <property name="successUrl" value="/ok.html" />
    </bean>
    <bean id="adminformAuthenticationFilter"
        class="cc.yihy.shiro.filter.NewFormAuthenticationFilter">
        <property name="loginUrl" value="/admin/login.html" />
        <property name="successUrl" value="/admin/ok.html" />
    </bean>
    <bean id="AdminRealm" class="cc.yihy.realm.AdminRealm">
        <property name="userService" ref="UserService" />
    </bean>
    <bean id="UserRealm" class="cc.yihy.realm.UserRealm">
        <property name="userService" ref="UserService" />
    </bean>
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- filter&realm綁定 -->
        <property name="authenticator" ref="DefineModularRealmAuthenticator" />
    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

到此,綁定工作完成甲抖。漆改。。

歡迎拍磚提問准谚。
用MarkDown寫博客感覺還不錯挫剑。
表示差點把Shiro開始創(chuàng)建執(zhí)行的過程寫上去。

在后面進行頁面上權限校驗會報錯柱衔,原因出在改變了realm的注入方式樊破。解決辦法,近期我會整理出來秀存。

權限校驗問題修復

權限校驗會報錯是因為修改了注入realm的位置捶码,導致授權解釋器拿不到realm集合,現(xiàn)在把授權解釋器重寫了或链,并注入realms惫恼,使其恢復正常



<!-- realms Map -->

    <bean id="defineRealms" class="org.springframework.beans.factory.config.MapFactoryBean">

        <property name="sourceMap">

            <map>

                <entry key="authc" value-ref="SSOUserRealm"/>

                <!--<entry key="NormalUser" value-ref="UserRealm"/>-->

                <!--<entry key="authUser" value-ref="UserRealm"/>-->

                <entry key="SSOUser" value-ref="SSOUserRealm"/>


                <entry key="mxUser" value-ref="AUserRealm"/>

                <entry key="normalMx" value-ref="AUserRealm"/>

                <entry key="mxAdmin" value-ref="AUserRealm"/>

                <entry key="subMxAdmin" value-ref="AUserRealm"/>


                <entry key="xhAdmin" value-ref="XhUserRealm"/>

                <entry key="xhUser" value-ref="XhUserRealm"/>

                <entry key="subXhUser" value-ref="XhUserRealm"/>

            </map>

        </property>

    </bean>

 <!-- filter&realm綁定 -->
    <!--認證解釋器-->
    <bean id="DefineModularRealmAuthenticator"

          class="cc.yihy.shiro.authc.pam.DefineModularRealmAuthenticator">

        <property name="defineRealms" ref="defineRealms"/>

    </bean>


    <!--授權解釋器-->
    <bean id="DefineModularRealmAuthorizer"

          class="cc.yihy.shiro.authz.DefineModularRealmAuthorizer">

        <property name="defineRealms" ref="defineRealms"/>

    </bean>

 <!-- securityManager安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">


        <!-- filter&realm綁定 登錄 -->

        <property name="authenticator" ref="DefineModularRealmAuthenticator"/>

        <!-- filter&realm綁定 角色權限驗證 -->

        <property name="authorizer" ref="DefineModularRealmAuthorizer"/>

        <!-- 注入緩存管理器 -->

        <property name="cacheManager" ref="ShiroCacheManager"/>

        <!-- 注入session管理器 -->

        <property name="sessionManager" ref="sessionManager"/>

        <!-- 記住我 -->

        <property name="rememberMeManager" ref="rememberMeManager"/>

    </bean>
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市澳盐,隨后出現(xiàn)的幾起案子祈纯,更是在濱河造成了極大的恐慌,老刑警劉巖叼耙,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腕窥,死亡現(xiàn)場離奇詭異,居然都是意外死亡筛婉,警方通過查閱死者的電腦和手機簇爆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門癞松,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人入蛆,你說我怎么就攤上這事响蓉。” “怎么了哨毁?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵枫甲,是天一觀的道長。 經(jīng)常有香客問我扼褪,道長想幻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任话浇,我火速辦了婚禮脏毯,結果婚禮上,老公的妹妹穿的比我還像新娘凳枝。我一直安慰自己徽曲,他們只是感情好旱易,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布薯酝。 她就那樣靜靜地躺著豺妓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蹋订。 梳的紋絲不亂的頭發(fā)上率挣,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機與錄音露戒,去河邊找鬼椒功。 笑死,一個胖子當著我的面吹牛智什,可吹牛的內(nèi)容都是我干的动漾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荠锭,長吁一口氣:“原來是場噩夢啊……” “哼旱眯!你這毒婦竟也來了?” 一聲冷哼從身側響起证九,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤删豺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后愧怜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呀页,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年拥坛,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓬蝶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尘分。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖丸氛,靈堂內(nèi)的尸體忽然破棺而出音诫,到底是詐尸還是另有隱情,我是刑警寧澤雪位,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站梨撞,受9級特大地震影響雹洗,放射性物質發(fā)生泄漏。R本人自食惡果不足惜卧波,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一时肿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧港粱,春花似錦螃成、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至偿曙,卻和暖如春氮凝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背望忆。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工罩阵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人启摄。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓稿壁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親歉备。 傳聞我的和親對象是個殘疾皇子傅是,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 文章轉載自:http://blog.csdn.net/w1196726224/article/details/53...
    wangzaiplus閱讀 3,397評論 0 3
  • 構建一個互聯(lián)網(wǎng)應用,權限校驗管理是很重要的安全措施威创,這其中主要包含: 認證 - 用戶身份識別落午,即登錄 授權 - 訪...
    zhuke閱讀 3,505評論 0 30
  • 一、架構 要學習如何使用Shiro必須先從它的架構談起肚豺,作為一款安全框架Shiro的設計相當精妙溃斋。Shiro的應用...
    ITsupuerlady閱讀 3,533評論 4 32
  • Shiro的使用方法參見跟我學shiro Shiro的驗證過程分析如下: 獲取SecurityManager 并綁...
    王兆陽閱讀 1,559評論 0 0
  • 勞動節(jié),我們需要休息吸申,不勞動啦梗劫,帶著小朋友去玩耍享甸。 不到公園不知道小朋友有多淘,先是各種瘋跑梳侨,到湖邊后就各種要下水...
    渦孩緹閱讀 105評論 0 0