前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >shiro单Realm实现多种登陆方式的扩展与实现

shiro单Realm实现多种登陆方式的扩展与实现

作者头像
星痕
发布2018-10-08 11:24:38
2.1K0
发布2018-10-08 11:24:38
举报

最近考虑给自己的平台增加新的登陆方式,上网查了一下相关的资料 .我用的权限平台为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;
    }


}
  1. 整理登陆方式需要实现的接口类
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

  1. 自己实现一种简单的普通登陆验证
@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+"");
    }
}
  1. 根据登陆方式,获取需要的实现类
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;
    }
}
  1. 改造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());
    }
  1. 修改密码验证方式
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));
    }
}
  1. 调整登陆接口
 @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.调整登陆接口

经测试,免密码登陆也是轻松就实现了

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.09.24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档