SpringBoot集成Shiro实现多数据源认证授权与分布式会话(二)

描述

继上一篇文章{% post_link SpringBoot集成Shiro实现多数据源认证授权与分布式会话(一) %}接下来我们再来看看shiro如何实现多数据源认证授权,由于在业务上的需要,我们系统提供了app端和pc端两种登录入口,app端又细分为手机号码登录和第三方应用登录两种渠道,再加上pc端后台登录一共有三种不同的认证渠道,用户数据也分别存储在两张不同的表结构中即app用户表和后台用户表,所以系统一共需要AppRealm(手机)和PcRealm(后台)以及ThirdRealm(第三方)三种不同的shiro realm,下面我们结合shiro来看看具体的实现步骤.

实现步骤

在一般情况下shiro默认只有一个realm,所有用户的认证授权都由这个realm来处理,当我们配置了多个realm的时候,shiro在认证时会根据不同的登录渠道调用相应的realm来处理认证操作,而鉴权过程则是根据controller层中的@RequiresPermissions注解来check用户是否有操作权限并在迭代realms集合中的元素时调用相应realm对象中的doGetAuthorizationInfo方法一直到shiro的认证器ModularRealmAuthorizer类中的isPermitted方法返回true为止否则继续迭代到循环体结束,具体可看以下shiro源码org.apache.shiro.authc.pam.ModularRealmAuthorizer类中的isPermitted方法.

    public boolean isPermitted(PrincipalCollection principals, String permission) {
        assertRealmsConfigured();
        for (Realm realm : getRealms()) {//getRealms获取到的是realms集合
            if (!(realm instanceof Authorizer)) continue;
            if (((Authorizer) realm).isPermitted(principals, permission)) {
                return true;//return true则停止迭代
            }
        }
        return false;
    }

所以在这里为了防止权限校验过程中由于频繁调用doGetAuthorizationInfo方法造成不必要的性能消耗以及实现不同的登录渠道调用不同的realm,我们先定义一个枚举类DeviceType来区分不同的登录设备类型.

public enum DeviceType {
    PC("Pc"), APP("App"), THIRDPATH("ThirdPath");
    private String type;
    DeviceType(String type) {
        this.type = type;
    }
    @Override
    public String toString() {
        return this.type;
    }
}

接下来我们还需要自定义用户/密码身份认证Token类并实现带参的构造方法以便我们可以将区分多端用户的属性传进去,该类扩展继承自shiro的UsernamePasswordToken,大概代码如下:

    /**
     * 记录设备类型 用于区分APP或者PC端用户
     */
    private String deviceType;
    /**
     * 记录登录类型 用于区分正常手机号码登录还是第三方应用登录
     */
    private Integer fromType;
    public CustomizedUsernamePasswordToken(final String userNamefinal String password, Integer fromType, String deviceType) {
        super(userName, password);
        this.setDeviceType(deviceType);
        this.setFromType(fromType);
    }
    省略get和set方法...

由于shiro默认使用的认证器是org.apache.shiro.authc.pam.ModularRealmAuthorizer,为了实现多realm认证必须自定义我们自己的认证器CustomizedModularRealmAuthenticator并覆写其中的doAuthenticate方法,这样就可以通过登录时传给token类的deviceType与realm数据源名称匹配决定当前认证调用的是哪个数据源,具体代码如下:

    /**
     * 重写doAuthenticate让APP帐号和PC帐号自动使用各自的Realm
     */
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        /**
         * 判断getRealms()是否返回为空
         */
        this.assertRealmsConfigured();
        /**
         * 强制转换回自定义的CustomizedUsernamePasswordToken
         */
        CustomizedUsernamePasswordToken customizedToken = (CustomizedUsernamePasswordToken) authenticationToken;
        /**
         * 登录设备类型
         */
        String deviceType = customizedToken.getDeviceType();
        /**
         * 所有自定义的Realm
         */
        Collection<Realm> customerRealms = this.getRealms();
        /**
         * 登录设备类型对应的所有自定义Realm
         */
        Collection<Realm> deviceRealms = new ArrayList<>();
        /**
         * 这里所有自定义的Realm的Name必须包含相对应的设备名
         */
        for (Realm realm : customerRealms) {
            if (realm.getName().contains(deviceType))
                deviceRealms.add(realm);
        }
        /**
         * 判断是单Realm还是多Realm
         */
        if (deviceRealms.size() == 1) {
            return doSingleRealmAuthentication(deviceRealms.iterator().next(),
                    customizedToken);
        } else {
            return doMultiRealmAuthentication(deviceRealms, customizedToken);
        }
    }

到这里基本上我们已经完成对多数据源realm认证的底层改造了,下一步实现自定义的realm很简单,只要继承自org.apache.shiro.AuthorizingRealm类并覆写其中的doGetAuthorizationInfo方法和doGetAuthenticationInfo方法就可以了,需要注意的是在认证时我们获取认证信息使用的是自定义的token类CustomizedUsernamePasswordToken而不再是默认的UsernamePasswordToken类,另外在做权限校验时可以通过将用户登录之后存储在session中的deviceType取出来判断这种方式来避免所有的realm都会执行一遍自己的doGetAuthorizationInfo方法的造成的性能消耗问题,如以下代码:

        Subject subject = SecurityUtils.getSubject();
        String deviceType = (String) subject.getSession().getAttribute(
                SessionCons.DEVICE_TYPE);
        if (deviceType.equals(DeviceType.APP.toString())) {//true则往下执行
            省略其他....
        }

最后一步是在配置shiro的安全管理器securityManager时要把实现的多个自定义数据源realm依次添加到realms集合中,再调用securityManager的setRealms方法将集合作为参数传进去.

        Collection<Realm> realms = new ArrayList<>();
        realms.add(appShiroRealm());
        realms.add(pcShiroRealm());
        realms.add(thirdPathShiroRealm());
        securityManager.setRealms(realms);
              其他略...

剩下的就是在controller层实现login操作了,注意要将不同登录渠道的deviceType传到自定义的token中,主要代码如下:

        // 登录结果回写用户信息和token
        Map<String, Object> responseDto = new HashMap<>();
        // 判断是否已经登录
        if (!subject.isAuthenticated()) {
            // 私钥解密后的账户密码 shiro用来认证登录
            CustomizedUsernamePasswordToken token;
            if (isThird) {
                //第三方应用使用openId登录
                token = new CustomizedUsernamePasswordToken(userAccount,
                        MD5.md5(userPassword), fromType, DeviceType.THIRDPATH.toString());
            } else {
                //正常App用户手机号码登录
                token = new CustomizedUsernamePasswordToken(userAccount,
                        MD5.md5(userPassword), fromType, DeviceType.APP.toString());
            }
            token.setRememberMe(false);
            subject.login(token);
            subject.getSession().setAttribute(SessionCons.DEVICE_TYPE,DeviceType.APP.toString());
            省略其他业务逻辑...
        }

到此我们已经完全实现了shiro的多数据源认证授权,下一篇再来看看如何结合redis的使用实现分布式session管理.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容