我们平常的系统建设中大多数都涉及到两种及以上的登陆方式,例如:手机号码登陆、用户名及密码登陆等,而此时我们是不是就得需要两个realm才能实现?
通常情况下我们一般都是用的shiro默认的认证器org.apache.shiro.authc.pam.ModularRealmAuthenticator,这里面决定使用realm的是doAuthenticate()方法。而此时我想指定使用我自定义的realm,该怎么去操作呢?请大家看下面的例子:
第一步:
定义一个区分登陆类型的枚举类(你不定义也行,此处定义只是个人习惯的编写方式而已)PHONE为手机号码登陆,USERNAME为用户名跟密码登陆:
public enum LoginType {
//枚举中对应的值是我们realm注入在spring当中Bean的命名
PHONE("ShiroUserPhoneRealm"), USERNAME("MyShiroRealm");
private String type;
private LoginType(String type) {
this.type = type;
}
@Override
public String toString() {
return this.type.toString();
}
}
第二步:
创建CustomToken类来继承shiro自带的UsernamePasswordToken类,我们新增了一个loginType字段来判断登陆类型:
public class CustomToken extends UsernamePasswordToken {
//登录类型,判断是手机号码登陆,还是账号密码登录
private String loginType;
public CustomToken(final String username, final String password, String loginType) {
super(username,password);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
第三步:
创建CustomModularRealmAuthenticator类继承ModularRealmAuthenticator,重写doAuthenticate方法用以指定使用我们的自定义realm,并将CustomModularRealmAuthenticator类注入到spring容器中。
@Component
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {
// 判断getRealms()是否返回为空
assertRealmsConfigured();
// 将authenticationToken强制转换为自定义的CustomToken
CustomToken customToken = (CustomToken) authenticationToken;
// 登录类型
String loginType = customToken.getLoginType();
// 所有realm
Collection<Realm> realms = getRealms();
// 用来存放登陆类型指定的realm
Collection<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
//根据我们传入的登陆类型来决定使用realm
if (realm.getName().contains(loginType))
typeRealms.add(realm);
}
// 判断是单realm还是多realm
if (typeRealms.size() == 1)
return doSingleRealmAuthentication(typeRealms.iterator().next(), customToken);
else
return doMultiRealmAuthentication(typeRealms, customToken);
}
}
第四步:
下图是我的LoginType为枚举值USERNAME("MyShiroRealm")是跳转的realm类(该类仅作参考):
/**
*
* loginType为USERNAME
**/
@Service
public class MyShiroRealm extends AuthorizingRealm {
private Logger log = LoggerFactory.getLogger(MyShiroRealm.class);
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Session session = SecurityUtils.getSubject().getSession();
UserVO userVO = (UserVO) session.getAttribute(Constants.USER_SESSION_KEY);
authorizationInfo.addStringPermissions(userVO.getStringPermissions());
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户的输入帐号和密码
String userInputAccount = authenticationToken.getPrincipal().toString();
// 获得用户提交的密码
String password = "";
if (authenticationToken.getCredentials() instanceof char[]) {
password = String.valueOf((char[]) authenticationToken.getCredentials());
} else {
password = authenticationToken.getCredentials().toString();
}
UserVO userVo = userService.findLoginUser(userInputAccount, IsUserParam.mobile);
if (userVo == null) {
throw new AccountException("不存在此用户");
}
if (userVo.getIsLocked() == 1){
throw new AccountException("账户已锁定");
}
// 密码是没有解密的,这里是为了跟数据库呼应
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userVo, // 用户名
userVo.getPassword(), // 密码
ByteSource.Util.bytes(userVo.getUsername()),
getName() // realm name
);
userVo.setPassword(null);
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute(Constants.USER_SESSION_KEY, userVo);
return authenticationInfo;
}
}
下图是我的LoginType为枚举值PHONE("ShiroUserPhoneRealm")是跳转的realm类(该类仅作参考):
/**
*
* LoginType为PHONE
**/
@Service
public class ShiroUserPhoneRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Session session = SecurityUtils.getSubject().getSession();
UserVO userVO = (UserVO) session.getAttribute(Constants.USER_SESSION_KEY);
authorizationInfo.addStringPermissions(userVO.getStringPermissions());
return authorizationInfo;
}
/**
* 通过此方法完成认证数据的获取及封装,系统底层会将认证数据传递认证管理器,有认证管理器完成认证操作
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
CustomToken token = null;
if (authenticationToken instanceof CustomToken) {
token = (CustomToken) authenticationToken;
} else {
return null;
}
String userInputAccount = authenticationToken.getPrincipal().toString();
UserVO userVO = userService.findByUserPhone(userInputAccount, IsUserParam.mobile);
if (userVO == null) {
throw new AccountException("不存在此用户");
}
if (userVO.getIsLocked() == 1){
throw new AccountException("账户已锁定");
}
userVO.setPassword(null);
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute(Constants.USER_SESSION_KEY, userVO);
return new SimpleAuthenticationInfo(userVO, userInputAccount, getName());
}
}
第五步:
配置我们的ShrioConfig,这里有xml、代码两种配置方式我这边使用的是代码的配置方式:
/**
* 多方式登陆,只需要继承AuthorizingRealm
* @return
*/
@Bean
public SecurityManager securityManager(Collection<Realm> realms, CustomModularRealmAuthenticator customModularRealmAuthenticator) {
DefaultWebSecurityManager sManager = new DefaultWebSecurityManager();
//此处需注意参数装载顺序
sManager.setAuthenticator(customModularRealmAuthenticator);
sManager.setRealms(realms);
return sManager;
}
如果各位码友需要了解xml的配置方式这里可以去看一下这位大佬写的文章:
https://blog.csdn.net/qq_33099301/article/details/84777016
第六步:
上述配置完成之后我们可以开始使用了,使用方式很简单:
Subject subject = SecurityUtils.getSubject();
//引用我们创建的CustomToken填入账号密码及登陆类型
//这种方式使用的LoginType.USERNAME方式所以他会进入我们的MyShiroRealm
CustomToken redisToken = new CustomToken(dbUserVO.getUsername(),password,LoginType.USERNAME.toString());
//这种方式使用的LoginType.PHONE方式所以他会进入我们的ShiroUserPhoneRealm
CustomToken redisToken = new CustomToken(dbUserVO.getUsername(),password,LoginType.PHONE.toString());
subject.login(redisToken);
这就实现了shiro指定自定义realm。