最近考虑给自己的平台增加新的登陆方式,上网查了一下相关的资料
.我用的权限平台为shiro,如果要实现,需要实现多个Realm,我个人觉得这种方法有点麻烦,每增加一种登陆方式,都要实现Realm,就希望有一些简单的办法.
整理需求如下:
- 支持普通的用户密码验证
- 密码验证可以让用户自由扩展,不一定是md5
- 支持用户免密码验证
- 新增登陆方式时,如需要新增手机号登陆,最少改动原有的代码
经思考,实现如下:
1.先实现自定义对象UsernamePasswordToken
@Data
public class UserNameLoginToken extends UsernamePasswordToken implements Serializable {
/**
* 登陆类型
*/
private String loginType;
public UserNameLoginToken() {
super();
}
public UserNameLoginToken(final String username, final String password) {
super(username, password);
}
/**
*是否需要密码校验
*/
private boolean requriedPassword;
public static UserNameLoginToken buildNoPassword(String username, String loginType) {
UserNameLoginToken userNameLoginToken = new UserNameLoginToken();
userNameLoginToken.setUsername(username);
userNameLoginToken.setLoginType(loginType);
userNameLoginToken.setRequriedPassword(false);
return userNameLoginToken;
}
public static UserNameLoginToken buildPassword(String username, String password, String loginType) {
UserNameLoginToken userNameLoginToken = new UserNameLoginToken(username, password);
userNameLoginToken.setLoginType(loginType);
userNameLoginToken.setRequriedPassword(true);
return userNameLoginToken;
}
}
- 整理登陆方式需要实现的接口类
public interface ILoginService {
/**
* 通过token查找用户的信息
* 可以是通过登陆名,也可以通过手机号,关键是UserNameLoginToken的构造成生成
* @param userNameLoginToken
* @return
*/
UserInfoDto loadUserByToken(UserNameLoginToken userNameLoginToken);
/**
* 判断是否是该登陆类型的实现类
* @param loginType
* @return
*/
boolean isSupportLogin(String loginType);
/**
* 判断登陆密码是否正确
* @param userNameLoginToken
* @param info 可以允许为空,如果密码为空,可以在info里查找后台密码
* @param password 密码
* @return
*/
boolean isPasswordMatch(UserNameLoginToken userNameLoginToken, AuthenticationInfo info,Object password);
}
所有登陆验证,都实现该接口ILoginService
- 自己实现一种简单的普通登陆验证
@Service
public class LoginCommonService implements ILoginService {
public static final String LOGINCOMMON_LOGINTYPE = "md5";
/**
* 组织服务API
*/
@Autowired
private ISysOrgApiService sysOrgApiService;
@Override
public UserInfoDto loadUserByToken(UserNameLoginToken userNameLoginToken) {
String username = (String) userNameLoginToken.getPrincipal();
return sysOrgApiService.loadUserByUsername(username);
}
@Override
public boolean isSupportLogin(String loginType) {
return LOGINCOMMON_LOGINTYPE.equalsIgnoreCase(loginType);
}
@Override
public boolean isPasswordMatch(UserNameLoginToken authcToken, AuthenticationInfo info, Object accountCredentials) {
Object tokenCredentials = MD5Util.getMD5String(authcToken.getUsername()
+ String.valueOf(authcToken.getPassword()));
return accountCredentials.equals(tokenCredentials+"");
}
}
- 根据登陆方式,获取需要的实现类
public class LoginServiceUtil {
public static ILoginService getLoginServcie(String loginType) {
//获取登陆方式接口的所有实现类
Map<String, ILoginService> loginServiceMap = SpringContextUtils.getApplicationContext().getBeansOfType(ILoginService.class);
for (ILoginService value : loginServiceMap.values()) {
if(value.isSupportLogin(loginType))return value;
}
return null;
}
}
- 改造Realm如下;
/* 主要是用来进行身份认证的,也就是说验证用户输入的账号和密是否正确。 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
UserNameLoginToken userNameLoginToken = (UserNameLoginToken) authcToken;
//根据登陆方式,获取相关人员对象,userNameLoginToken里的username可能是登陆名,手机号,邮箱,不确实,可扩展
UserInfoDto userInfo = LoginServiceUtil.getLoginServcie(userNameLoginToken.getLoginType()).loadUserByToken(userNameLoginToken);
if (null == userInfo) {
throw new AuthenticationException("用户不存在");
}
if(userNameLoginToken.isRequriedPassword()&&
!LoginServiceUtil.getLoginServcie(userNameLoginToken.getLoginType()).isPasswordMatch(userNameLoginToken,null,userInfo.getPassword())){
throw new IncorrectCredentialsException("密码错误");
}
SecurityUser securityUser = new SecurityUser();
securityUser.setUserInfo(userInfo);
// 匹配管理员角色
Optional.ofNullable(userInfo.getOrgIds()).ifPresent(orgIds -> {
securityUser.setRoles(sysAuthApiService.getRoleIdsByOrgId(orgIds));
securityUser.setPermissionSet(sysAuthApiService.getPermissionIdsByOrgId(orgIds));
});
// 匹配管理员角色
Optional.ofNullable(securityUser.getRoles()).ifPresent(roles -> {
boolean isAdmin = roles.stream().anyMatch(role -> SecurityConstants.SUPER_ADMIN.equals(role));
securityUser.setAdmin(isAdmin);
});
return new SimpleAuthenticationInfo(securityUser, securityUser.getUserInfo().getPassword(), getName());
}
- 修改密码验证方式
public class CredentialsMatcher extends SimpleCredentialsMatcher {
private final static Logger LOGGER = LoggerFactory.getLogger(CredentialsMatcher.class);
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UserNameLoginToken userNameLoginToken = (UserNameLoginToken) token;
//如果是免密码登陆,直接返回
if (!userNameLoginToken.isRequriedPassword()) {
return true;
}
return LoginServiceUtil.getLoginServcie(userNameLoginToken.getLoginType()).isPasswordMatch(userNameLoginToken, info, getCredentials(info));
}
}
- 调整登陆接口
@ApiOperation(value = "登陆")
@RequestMapping(
value = "/login",
method = RequestMethod.POST
)
public SuccessResponseData login(@RequestBody LoginUserVo loginUserVo) {
SuccessResponseData successResponse = new SuccessResponseData();
UserNameLoginToken token = UserNameLoginToken.buildNoPassword(loginUserVo.getUsername(),"nopassword");
// UserNameLoginToken token = UserNameLoginToken.buildPassword(loginUserVo.getUsername(),loginUserVo.getPassword(),"md5");
Subject subject = SecurityUtils.getSubject();
subject.login(token);
successResponse.setData(subject.getSession().getId());
successResponse.setMsg("登录成功");
System.out.println(JSON.toJSONString(successResponse));
return successResponse;
}
登陆时,根据需要的登陆方式构造UserNameLoginToken即可
新增新的登陆方式,只需要做两步.
1.实现接口类ILoginService
2.调整登陆接口
经测试,免密码登陆也是轻松就实现了