Shiro一款非常好用的权限框架,轻量、非常容易上手,在项目中,我们往往会遇到多种用户类型登陆的情况,比如管理员、网站会员等,他们都是属于不同的数据库表,显然一个realm验证是不满足,今天来记录下多realm的实现。
实现原理
最开始尝试写了多个realm,然后配置进去,发现每次验证都会走所有的realm,与我们实际业务不符合,后面发现ModularRealmAuthenticator和ModularRealmAuthorizer这2个类提供了认证和授权,我们可以改写这个方法,实现不同类型走不同realm认证和授权。
实现过程
自定义一个token类继承UsernamePasswordToken,在这个类中我们增加一个type类型枚举LoginTypeEnum,标记用户属于管理员登陆还是网站会员登陆。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class MyUsernamePasswordToken extends UsernamePasswordToken {
    /**
     * 登录类型
     */
    private LoginTypeEnum loginType;
    public MyUsernamePasswordToken(String username, String password, LoginTypeEnum loginType, boolean rememberMe) {
        super(username, password, rememberMe, null);
        this.loginType = loginType;
    }
    public LoginTypeEnum getLoginType() {
        return loginType;
    }
}
自定义一个类继承ModularRealmAuthenticator,我们重写他的doAuthenticate方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        // 断言Realm是否配置了
        assertRealmsConfigured();
        MyUsernamePasswordToken token = (MyUsernamePasswordToken) authenticationToken;
        // 登录类型
        String loginType = token.getLoginType().getType();
        // 所有Realm
        Collection<Realm> realms = getRealms();
        // 登录类型对应的所有Realm
        Collection<Realm> typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
             //如果类型匹配上了,我们就添加进去
            if (realm.getName().contains(loginType)){
                typeRealms.add(realm);
            }
        }
        // 判断是单Realm还是多Realm
        if (typeRealms.size() == 1) {
            return doSingleRealmAuthentication(typeRealms.iterator().next(), token);
        }
        else {
            return doMultiRealmAuthentication(typeRealms, token);
        }
    }
}
自定义一个类继承ModularRealmAuthorizer,我们重写他的isPermitted方法,当然这里真正意义上还需要重新其他授权验证的方法,比如是否有XX角色的验证(hasRole),我这里就只写一个了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MyModularRealmAuthorizer extends ModularRealmAuthorizer {
    @Override
    public boolean isPermitted(PrincipalCollection principals, String permission) {
        //断言配置
        assertRealmsConfigured();
        //获取到所有realm的名称
        Set<String> realmNames = principals.getRealmNames();
        for (String realmName : realmNames) {
            //遍历所有的realm
            for (Realm realm : realms) {
                if (!(realm instanceof Authorizer)) {
                    continue;
                }
                //如果名称匹配上了,就走这个realm权限验证
                if (realm.getName().contains(realmName)) {
                    return ((Authorizer) realm).isPermitted(principals, permission);
                }
            }
        }
        return false;
    }
}
将我们自定义的MyModularRealmAuthenticator和ModularRealmAuthorizer加入到shiro配置中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38/**
 * 管理员验证realm
 * @return
 */
@Bean
public AdminRealm adminRealm() {
    AdminRealm adminRealm = new AdminRealm();
    //指定类型 标志为admin登陆
    adminRealm.setName(LoginTypeEnum.ADMIN_LOGIN.getType());
    return adminRealm;
}
/**
 * 网站会员realm
 * @return
 */
@Bean
public MemberRealm memberRealm() {
    MemberRealm memberRealm = new MemberRealm();
    //指定类型 标志为member登陆
    memberRealm.setName(LoginTypeEnum.MEMBER_LOGIN.getType());
    return memberRealm;
}
@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //可以指定特定Realm处理认证
    securityManager.setAuthenticator(new MyModularRealmAuthenticator());
    //可以指定特定Realm处理授权验证
    securityManager.setAuthorizer(new MyModularRealmAuthorizer());
    List<Realm> realmList = new ArrayList<>();
    realmList.add(adminRealm());
    realmList.add(memberRealm());
    //这个放到后面,可以自动把realms赋值给Authenticator
    securityManager.setRealms(realmList);
    return securityManager;
}
核心东西都差不多写完了,最后在登陆的方法里,我们给MyUsernamePasswordToken指定好类型就可以进行不同的realm验证了。1
2
3
4
5
6
7
8
9
10
11
12public String doLogin(String username, String password) {
    Subject subject = SecurityUtils.getSubject();
    //此处记得注入类型,多realm依靠这个类型来区别验证
    MyUsernamePasswordToken token = new MyUsernamePasswordToken(username, password, LoginTypeEnum.ADMIN_LOGIN,false);
    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        //验证失败
    }
    //登陆成功
    return subject.getSession().getId().toString()
}
到这里多realm就完成了,这里代码只是记录了关键的代码,关键点就是重写shiro的ModularRealmAuthenticator(登陆认证)和ModularRealmAuthorizer(授权验证)里面的方法,通过登陆的时候指定一个类型来做到区别多realm。