jeecg基于shiro(多realm) + jwt實現(xiàn)前后端登陸認證

首先shiro是啥我就不多說了,總得概括就是,以subject為主,在調(diào)用subject的時候,都會將任務(wù)委托給SecurityManager,SecurityManager類似于一個中轉(zhuǎn)站,不過在寫代碼時一般不需要去管他,因為shiro主張的是將主體的認證和權(quán)限管理交由用戶自己定義,所以只需要自定以完realm后注入到securityManager就行。

上jeecg代碼,這里做了修改注入了多個realm

        @Bean("securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        List<Realm> realms = new ArrayList<>();
        //添加多個Realm
        realms.add(new FrontShiroRealm());
        realms.add(new ShiroRealm());
        securityManager.setRealms(realms);

        /*
         * 關(guān)閉shiro自帶的session,詳情見文檔
         * http://shiro.apache.org/session-management.html#SessionManagement-
         * StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //自定義緩存實現(xiàn),使用redis
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

上面說到在調(diào)用subject時,會把任務(wù)委托給securityManager,具體是怎么實現(xiàn)的呢?
下面是重點:
在shiro實現(xiàn)登錄功能時,Subject的實現(xiàn)類會調(diào)用Authenticator這個接口的默認實現(xiàn)類ModularRealmAuthenticator來進行帳號密碼以及驗證碼的驗證,非常重要的一點是,和 Realm 交互的 ModularRealmAuthenticator 按迭代(iteration) 順序執(zhí)行。ModularRealmAuthenticator 可以訪問為SecurityManager 配置的 Realm 實例,當嘗試一次驗證時,它將在集合中遍歷,支持對提交的 AuthenticationToken 處理的每個 Realm 都將執(zhí)行 Realm 的 getAuthenticationInfo 方法。
參考博客:mkdeveloper的博客http://developer.mksoft.cn/
下面的方法注入了一個自定義的ModularRealmAuthenticator,并配置了驗證策略為AtLeastOneSuccessfulStrategy

 /**
     * 系統(tǒng)自帶的Realm管理,主要針對多realm
     * */
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        //自己重寫的ModularRealmAuthenticator
        UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());//這里為默認策略:如果有一個或多個Realm驗證成功,所有的嘗試都被認為是成功的,如果沒有一個驗證成功,則該次嘗試失敗
        return modularRealmAuthenticator;
    }

下圖是shiro支持的多realm認證策略:


image.png

那么我們下面重新自定義一個ModularRealmAuthenticator,根據(jù)tokn中的type來判斷登陸是來自前端還是后端。

package org.jeecg.config;

import lombok.extern.slf4j.Slf4j;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.util.CollectionUtils;
import org.jeecg.common.util.LoginTypeEnum;
import org.jeecg.modules.shiro.authc.JwtToken;

@Slf4j
public class DefineModularRealmAuthenticator extends ModularRealmAuthenticator {

    /**
     * 將Realm的實現(xiàn)類作為Map傳入,這樣便可以分清除前后臺登錄
     */
    private Map<String, Object> defineRealms;

    /**
     * 調(diào)用單個Realm來進行驗證
     */
    @Override
    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,AuthenticationToken token) {
        if (!realm.supports(token)) {
            String msg = "Realm ["
                    + realm
                    + "] does not support authentication token ["
                    + token
                    + "].  Please ensure that the appropriate Realm implementation is "
                    + "configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        }
        AuthenticationInfo info = null;
        try {
            info = realm.getAuthenticationInfo(token);
            if (info == null) {
                String msg = "Realm [" + realm
                        + "] was unable to find account data for the "
                        + "submitted AuthenticationToken [" + token + "].";
                throw new UnknownAccountException(msg);
            }
        }   catch (IncorrectCredentialsException e) {
            throw e;
        }  catch (UnknownAccountException e) {
            throw e;
        }catch (Throwable throwable) {
            if (log.isDebugEnabled()) {
                String msg = "Realm ["
                        + realm
                        + "] threw an exception during a multi-realm authentication attempt:";
                log.debug(msg,throwable );
            }

        }

        return info;
    }

    /**
     * 判斷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);
        }
    }
    /**
     * 這個方法比較重要,用來判斷此次調(diào)用是前臺還是后臺
     */
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        assertRealmsConfigured();
        JwtToken jwtToken = (JwtToken) authenticationToken;
        Realm realm = null;
        // 前端登錄
        if (StringUtils.equals(jwtToken.getLoginType(),
                LoginTypeEnum.FRONT.toString())) {
            realm = (Realm) defineRealms.get("customerRealm");
        }
        // 后臺登錄
        if (StringUtils
                .equals(jwtToken.getLoginType(), LoginTypeEnum.BACK.toString())) {
            realm = (Realm) defineRealms.get("adminRealm");

        }
        if(realm==null){
            return null;
        }
        return doSingleRealmAuthentication(realm, authenticationToken);

    }

    public void setDefineRealms(Map<String, Object> defineRealms) {
        this.defineRealms = defineRealms;
    }

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

最后將其注入到SecurityManager中就大功告成啦

@Bean("securityManager")
    public DefaultWebSecurityManager securityManager(FrontShiroRealm frontShiroRealm,ShiroRealm shiroRealm,DefineModularRealmAuthenticator defineModularRealmAuthenticator) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setAuthenticator(defineModularRealmAuthenticator);
        List<Realm> realms = new ArrayList<>();
        //添加多個Realm
        realms.add(frontShiroRealm);
        realms.add(shiroRealm);
        securityManager.setRealms(realms);

        /*
         * 關(guān)閉shiro自帶的session,詳情見文檔
         * http://shiro.apache.org/session-management.html#SessionManagement-
         * StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //自定義緩存實現(xiàn),使用redis
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

總結(jié)下:


image.png

2019-11-02 晴
今天天氣很棒,陽光正好,坐在窗口碼代碼,望著外面的天空,夾雜著細細碎碎的聲音,生活要是一直如此簡單多好

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

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