美文网首页
jeecg基于shiro(多realm) + jwt实现前后端登

jeecg基于shiro(多realm) + jwt实现前后端登

作者: 18372a74d8b8 | 来源:发表于2019-11-02 16:15 被阅读0次

    首先shiro是啥我就不多说了,总得概括就是,以subject为主,在调用subject的时候,都会将任务委托给SecurityManager,SecurityManager类似于一个中转站,不过在写代码时一般不需要去管他,因为shiro主张的是将主体的认证和权限管理交由用户自己定义,所以只需要自定以完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);
    
            /*
             * 关闭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);
            //自定义缓存实现,使用redis
            securityManager.setCacheManager(redisCacheManager());
            return securityManager;
        }
    

    上面说到在调用subject时,会把任务委托给securityManager,具体是怎么实现的呢?
    下面是重点:
    在shiro实现登录功能时,Subject的实现类会调用Authenticator这个接口的默认实现类ModularRealmAuthenticator来进行帐号密码以及验证码的验证,非常重要的一点是,和 Realm 交互的 ModularRealmAuthenticator 按迭代(iteration) 顺序执行。ModularRealmAuthenticator 可以访问为SecurityManager 配置的 Realm 实例,当尝试一次验证时,它将在集合中遍历,支持对提交的 AuthenticationToken 处理的每个 Realm 都将执行 Realm 的 getAuthenticationInfo 方法。

    下面的方法注入了一个自定义的ModularRealmAuthenticator,并配置了验证策略为AtLeastOneSuccessfulStrategy

     /**
         * 系统自带的Realm管理,主要针对多realm
         * */
        @Bean
        public ModularRealmAuthenticator modularRealmAuthenticator(){
            //自己重写的ModularRealmAuthenticator
            UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
            modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());//这里为默认策略:如果有一个或多个Realm验证成功,所有的尝试都被认为是成功的,如果没有一个验证成功,则该次尝试失败
            return modularRealmAuthenticator;
        }
    

    下图是shiro支持的多realm认证策略:


    image.png

    那么我们下面重新自定义一个ModularRealmAuthenticator,根据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的实现类作为Map传入,这样便可以分清除前后台登录
         */
        private Map<String, Object> defineRealms;
    
        /**
         * 调用单个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);
            }
        }
        /**
         * 这个方法比较重要,用来判断此次调用是前台还是后台
         */
        @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);
    
            /*
             * 关闭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);
            //自定义缓存实现,使用redis
            securityManager.setCacheManager(redisCacheManager());
            return securityManager;
        }
    

    总结下:


    image.png

    2019-11-02 晴
    今天天气很棒,阳光正好,坐在窗口码代码,望着外面的天空,夹杂着细细碎碎的声音,生活要是一直如此简单多好

    相关文章

      网友评论

          本文标题:jeecg基于shiro(多realm) + jwt实现前后端登

          本文链接:https://www.haomeiwen.com/subject/qurcbctx.html