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。