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

首先shiro是啥我就不多说了,总得概括就是,以subject为主,在调用subject的时候,都会将任务委托给SecurityManager,SecurityManager类似于一个中转站,不过在写代码时一般不需要去管他,因为shiro主张的是将主体的认证和权限管理交由用户自己定义,所以只需要自定以完realm后注入到securityManager就行。

上jeecg代码,这里做了修改注入了多个realm

        @Bean("securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        List 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 方法。
参考博客:mkdeveloper的博客http://developer.mksoft.cn/
下面的方法注入了一个自定义的ModularRealmAuthenticator,并配置了验证策略为AtLeastOneSuccessfulStrategy

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

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


jeecg基于shiro(多realm) + jwt实现前后端登陆认证_第1张图片
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 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 defineRealms) {
        this.defineRealms = defineRealms;
    }

    public Map getDefineRealms() {
        return defineRealms;
    }
}

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

@Bean("securityManager")
    public DefaultWebSecurityManager securityManager(FrontShiroRealm frontShiroRealm,ShiroRealm shiroRealm,DefineModularRealmAuthenticator defineModularRealmAuthenticator) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setAuthenticator(defineModularRealmAuthenticator);
        List 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实现前后端登陆认证)